From 39d85fa1aa6a31bd63b8fe9e634191411a4fc504 Mon Sep 17 00:00:00 2001 From: Johannes Pohl Date: Fri, 3 Feb 2017 15:24:41 +0100 Subject: [PATCH 1/4] add insert sine plugin context menu entry in EGV + dialog --- setup.py | 2 +- .../plugins/InsertSine/InsertSinePlugin.py | 65 +++++++ src/urh/plugins/InsertSine/__init__.py | 0 src/urh/plugins/InsertSine/descr.txt | 3 + .../plugins/InsertSine/insert_sine_dialog.ui | 177 ++++++++++++++++++ src/urh/plugins/InsertSine/settings.ui | 47 +++++ .../NetworkSDRInterfacePlugin.py | 3 +- src/urh/plugins/Plugin.py | 8 +- src/urh/ui/views/EpicGraphicView.py | 20 ++ src/urh/util/Formatter.py | 6 +- 10 files changed, 324 insertions(+), 7 deletions(-) create mode 100644 src/urh/plugins/InsertSine/InsertSinePlugin.py create mode 100644 src/urh/plugins/InsertSine/__init__.py create mode 100644 src/urh/plugins/InsertSine/descr.txt create mode 100644 src/urh/plugins/InsertSine/insert_sine_dialog.ui create mode 100644 src/urh/plugins/InsertSine/settings.ui diff --git a/setup.py b/setup.py index 1a37e8a7..91d5a49a 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ COMPILER_DIRECTIVES = {'language_level': 3, } UI_SUBDIRS = ("actions", "delegates", "views") -PLUGINS = ("AmbleAnalyzer", "MessageBreak", "ZeroHide", "NetworkSDRInterface") +PLUGINS = ("AmbleAnalyzer", "MessageBreak", "ZeroHide", "NetworkSDRInterface", "InsertSine") URH_DIR = "urh" try: diff --git a/src/urh/plugins/InsertSine/InsertSinePlugin.py b/src/urh/plugins/InsertSine/InsertSinePlugin.py new file mode 100644 index 00000000..4b746dd7 --- /dev/null +++ b/src/urh/plugins/InsertSine/InsertSinePlugin.py @@ -0,0 +1,65 @@ +import os + +from PyQt5 import uic +from PyQt5.QtCore import pyqtSlot +from PyQt5.QtWidgets import QDialog + +from urh.plugins.Plugin import SignalEditorPlugin +from urh.util.Formatter import Formatter + + +class InsertSinePlugin(SignalEditorPlugin): + def __init__(self): + dirname = os.path.dirname(os.readlink(__file__)) if os.path.islink(__file__) else os.path.dirname(__file__) + self.dialog_ui = uic.loadUi(os.path.realpath(os.path.join(dirname, "insert_sine_dialog.ui"))) # type: QDialog + + self.amplitude = 1 + self.frequency = 1e6 + self.phase = 0 + self.sample_rate = 1e6 + self.num_samples = 1e6 + + 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.labelTime.setText(Formatter.science_time(self.num_samples/self.sample_rate, decimals=3)) + + super().__init__(name="InsertSine") + + def create_connects(self): + pass + + def __create_dialog_connects(self): + self.dialog_ui.doubleSpinBoxAmplitude.valueChanged.connect(self.on_double_spin_box_amplitude_value_changed) + self.dialog_ui.doubleSpinBoxFrequency.valueChanged.connect(self.on_double_spin_box_frequency_value_changed) + self.dialog_ui.doubleSpinBoxPhase.valueChanged.connect(self.on_double_spin_box_phase_value_changed) + self.dialog_ui.doubleSpinBoxSampleRate.valueChanged.connect(self.on_double_spin_box_sample_rate_value_changed) + self.dialog_ui.doubleSpinBoxNSamples.valueChanged.connect(self.on_spin_box_n_samples_value_changed) + + def show_insert_sine_dialog(self): + self.dialog_ui.show() + self.__create_dialog_connects() + + @pyqtSlot(float) + def on_double_spin_box_amplitude_value_changed(self, value: float): + self.amplitude = value + + @pyqtSlot(float) + def on_double_spin_box_frequency_value_changed(self, value: float): + self.frequency = value + + @pyqtSlot(float) + def on_double_spin_box_phase_value_changed(self, value: float): + self.phase = value + + @pyqtSlot(float) + def on_double_spin_box_sample_rate_value_changed(self, value: float): + self.sample_rate = value + self.dialog_ui.labelTime.setText(Formatter.science_time(self.num_samples / self.sample_rate, decimals=3)) + + @pyqtSlot(float) + def on_spin_box_n_samples_value_changed(self, value: float): + self.num_samples = int(value) + self.dialog_ui.labelTime.setText(Formatter.science_time(self.num_samples / self.sample_rate, decimals=3)) diff --git a/src/urh/plugins/InsertSine/__init__.py b/src/urh/plugins/InsertSine/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/urh/plugins/InsertSine/descr.txt b/src/urh/plugins/InsertSine/descr.txt new file mode 100644 index 00000000..bf2ab2ca --- /dev/null +++ b/src/urh/plugins/InsertSine/descr.txt @@ -0,0 +1,3 @@ +This plugin enables you to insert custom sine waves into your signal. +You will find a new context menu entry in interpretation signal view. +Transform URH into a full fledged signal editor! \ No newline at end of file diff --git a/src/urh/plugins/InsertSine/insert_sine_dialog.ui b/src/urh/plugins/InsertSine/insert_sine_dialog.ui new file mode 100644 index 00000000..0b9efe24 --- /dev/null +++ b/src/urh/plugins/InsertSine/insert_sine_dialog.ui @@ -0,0 +1,177 @@ + + + DialogCustomSine + + + + 0 + 0 + 601 + 308 + + + + Insert sine wave + + + + + + Qt::Horizontal + + + + + + + Sample Rate: + + + + + + + 3 + + + 0.000000000000000 + + + 9999999999999999455752309870428160.000000000000000 + + + + + + + ° + + + 3 + + + 360.000000000000000 + + + + + + + 3 + + + 1.000000000000000 + + + 0.001000000000000 + + + 1.000000000000000 + + + + + + + Frequency (Hz): + + + + + + + 3 + + + 0.000000000000000 + + + 9999999999999999931398190359470212947659194368.000000000000000 + + + + + + + Phase: + + + + + + + 3 + + + 0.000000000000000 + + + 99999999999999991433150857216.000000000000000 + + + + + + + Samples: + + + + + + + Amplitude: + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Time: + + + + + + + 20s + + + + + + + + + + + + + KillerDoubleSpinBox + QDoubleSpinBox +
urh.ui.KillerDoubleSpinBox.h
+
+
+ + +
diff --git a/src/urh/plugins/InsertSine/settings.ui b/src/urh/plugins/InsertSine/settings.ui new file mode 100644 index 00000000..d47e3378 --- /dev/null +++ b/src/urh/plugins/InsertSine/settings.ui @@ -0,0 +1,47 @@ + + + InsertSineWaveSettings + + + + 0 + 0 + 400 + 300 + + + + Frame + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + No settings available. + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py b/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py index e61d1191..246817d1 100644 --- a/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py +++ b/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py @@ -2,10 +2,9 @@ import socketserver import threading import time -from PyQt5.QtCore import QRegExp, pyqtSlot +from PyQt5.QtCore import pyqtSlot from PyQt5.QtCore import QTimer from PyQt5.QtCore import pyqtSignal -from PyQt5.QtGui import QRegExpValidator import socket from urh.plugins.Plugin import SDRPlugin diff --git a/src/urh/plugins/Plugin.py b/src/urh/plugins/Plugin.py index 0c2f95d1..72b87a12 100644 --- a/src/urh/plugins/Plugin.py +++ b/src/urh/plugins/Plugin.py @@ -70,4 +70,10 @@ class LabelAssignPlugin(Plugin): class SDRPlugin(Plugin): def __init__(self, name: str): - Plugin.__init__(self, name) \ No newline at end of file + Plugin.__init__(self, name) + + +class SignalEditorPlugin(Plugin): + def __init__(self, name: str): + Plugin.__init__(self, name) + diff --git a/src/urh/ui/views/EpicGraphicView.py b/src/urh/ui/views/EpicGraphicView.py index 8272a000..cf4a1324 100644 --- a/src/urh/ui/views/EpicGraphicView.py +++ b/src/urh/ui/views/EpicGraphicView.py @@ -5,6 +5,8 @@ from PyQt5.QtWidgets import QAction from PyQt5.QtWidgets import QApplication, QMenu, QActionGroup from urh import constants +from urh.plugins.InsertSine.InsertSinePlugin import InsertSinePlugin +from urh.plugins.PluginManager import PluginManager from urh.ui.ROI import ROI from urh.ui.views.SelectableGraphicView import SelectableGraphicView @@ -66,6 +68,14 @@ class EpicGraphicView(SelectableGraphicView): self.delete_action.triggered.connect(self.on_delete_action_triggered) self.addAction(self.delete_action) + self.insert_sine_action = QAction(self.tr("Insert sine wave...")) + font = self.insert_sine_action.font() + font.setBold(True) + self.insert_sine_action.setFont(font) + self.insert_sine_action.triggered.connect(self.on_insert_sine_action_triggered) + self.insert_sine_plugin = InsertSinePlugin() + + @property def signal(self): return self.parent_frame.signal @@ -118,6 +128,7 @@ class EpicGraphicView(SelectableGraphicView): menu = QMenu(self) menu.addAction(self.save_action) menu.addAction(self.save_as_action) + menu.addSeparator() zoom_action = None create_action = None @@ -131,6 +142,11 @@ class EpicGraphicView(SelectableGraphicView): self.paste_action.setEnabled(self.stored_item is not None) menu.addSeparator() + if PluginManager().is_plugin_enabled("InsertSine"): + menu.addAction(self.insert_sine_action) + if not self.selection_area.is_empty: + menu.addSeparator() + #autorangeAction = menu.addAction(self.tr("Show Autocrop Range")) if not self.selection_area.is_empty: @@ -353,3 +369,7 @@ class EpicGraphicView(SelectableGraphicView): def on_save_as_action_triggered(self): self.save_as_clicked.emit() + @pyqtSlot() + def on_insert_sine_action_triggered(self): + self.insert_sine_plugin.show_insert_sine_dialog() + diff --git a/src/urh/util/Formatter.py b/src/urh/util/Formatter.py index 04434e2a..6ebd92c2 100644 --- a/src/urh/util/Formatter.py +++ b/src/urh/util/Formatter.py @@ -12,7 +12,7 @@ class Formatter: @staticmethod - def science_time(time_in_seconds: float) -> str: + def science_time(time_in_seconds: float, decimals=2) -> str: if time_in_seconds < 1e-6: suffix = "n" value = time_in_seconds * 1e9 @@ -26,7 +26,7 @@ class Formatter: suffix = "" value = time_in_seconds - return locale.format_string("%.2f " + suffix, value) + "s" + return locale.format_string("%.{0}f ".format(decimals) + suffix, value) + "s" @staticmethod def big_value_with_suffix(value: float, decimals=3) -> str: @@ -47,4 +47,4 @@ class Formatter: return dtype(str_val) except (ValueError, TypeError): logger.warning("The {0} is not a valid {1}, assuming {2}".format(str_val, str(dtype), str(default))) - return default \ No newline at end of file + return default From ce901e4edd8f532e69345d0dd2ed831479323e22 Mon Sep 17 00:00:00 2001 From: jopohl Date: Fri, 3 Feb 2017 22:42:06 +0100 Subject: [PATCH 2/4] add preview of sine wave --- src/urh/plugins/InsertSine/InsertSinePlugin.py | 14 ++++++++++++++ src/urh/plugins/InsertSine/insert_sine_dialog.ui | 9 +++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/urh/plugins/InsertSine/InsertSinePlugin.py b/src/urh/plugins/InsertSine/InsertSinePlugin.py index 4b746dd7..5cb6377b 100644 --- a/src/urh/plugins/InsertSine/InsertSinePlugin.py +++ b/src/urh/plugins/InsertSine/InsertSinePlugin.py @@ -1,5 +1,6 @@ import os +import numpy as np from PyQt5 import uic from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import QDialog @@ -42,24 +43,37 @@ class InsertSinePlugin(SignalEditorPlugin): self.dialog_ui.show() self.__create_dialog_connects() + def draw_sine_wave(self): + self.complex_wave = np.empty(int(self.num_samples), dtype=np.complex64) + t = (np.arange(0, self.num_samples) / self.sample_rate) + self.complex_wave.real = self.amplitude * np.cos(2 * np.pi * self.frequency * t + self.phase) + self.complex_wave.imag = self.amplitude * np.sin(2 * np.pi * self.frequency * t + self.phase) + + self.dialog_ui.graphicsViewSineWave.plot_data(self.complex_wave.imag.astype(np.float32)) + @pyqtSlot(float) def on_double_spin_box_amplitude_value_changed(self, value: float): self.amplitude = value + self.draw_sine_wave() @pyqtSlot(float) def on_double_spin_box_frequency_value_changed(self, value: float): self.frequency = value + self.draw_sine_wave() @pyqtSlot(float) def on_double_spin_box_phase_value_changed(self, value: float): self.phase = value + self.draw_sine_wave() @pyqtSlot(float) def on_double_spin_box_sample_rate_value_changed(self, value: float): self.sample_rate = value self.dialog_ui.labelTime.setText(Formatter.science_time(self.num_samples / self.sample_rate, decimals=3)) + self.draw_sine_wave() @pyqtSlot(float) def on_spin_box_n_samples_value_changed(self, value: float): self.num_samples = int(value) self.dialog_ui.labelTime.setText(Formatter.science_time(self.num_samples / self.sample_rate, decimals=3)) + self.draw_sine_wave() diff --git a/src/urh/plugins/InsertSine/insert_sine_dialog.ui b/src/urh/plugins/InsertSine/insert_sine_dialog.ui index 0b9efe24..57505c1c 100644 --- a/src/urh/plugins/InsertSine/insert_sine_dialog.ui +++ b/src/urh/plugins/InsertSine/insert_sine_dialog.ui @@ -19,7 +19,7 @@ Qt::Horizontal - + @@ -160,12 +160,17 @@ - + + + ZoomableGraphicView + QGraphicsView +
urh.ui.views.ZoomableGraphicView.h
+
KillerDoubleSpinBox QDoubleSpinBox From 43d0ed15c6ecdd4e6b65f7aa27ee9e6e26bd2cea Mon Sep 17 00:00:00 2001 From: jopohl Date: Sat, 4 Feb 2017 19:08:26 +0100 Subject: [PATCH 3/4] draw anti aliased preview of signal --- src/urh/plugins/InsertSine/InsertSinePlugin.py | 4 +++- src/urh/plugins/InsertSine/insert_sine_dialog.ui | 6 +++++- src/urh/ui/views/ZoomableGraphicView.py | 7 ++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/urh/plugins/InsertSine/InsertSinePlugin.py b/src/urh/plugins/InsertSine/InsertSinePlugin.py index 5cb6377b..34d020df 100644 --- a/src/urh/plugins/InsertSine/InsertSinePlugin.py +++ b/src/urh/plugins/InsertSine/InsertSinePlugin.py @@ -15,7 +15,7 @@ class InsertSinePlugin(SignalEditorPlugin): self.dialog_ui = uic.loadUi(os.path.realpath(os.path.join(dirname, "insert_sine_dialog.ui"))) # type: QDialog self.amplitude = 1 - self.frequency = 1e6 + self.frequency = 10 self.phase = 0 self.sample_rate = 1e6 self.num_samples = 1e6 @@ -42,6 +42,7 @@ class InsertSinePlugin(SignalEditorPlugin): def show_insert_sine_dialog(self): self.dialog_ui.show() self.__create_dialog_connects() + self.draw_sine_wave() def draw_sine_wave(self): self.complex_wave = np.empty(int(self.num_samples), dtype=np.complex64) @@ -50,6 +51,7 @@ class InsertSinePlugin(SignalEditorPlugin): self.complex_wave.imag = self.amplitude * np.sin(2 * np.pi * self.frequency * t + self.phase) self.dialog_ui.graphicsViewSineWave.plot_data(self.complex_wave.imag.astype(np.float32)) + self.dialog_ui.graphicsViewSineWave.draw_full() @pyqtSlot(float) def on_double_spin_box_amplitude_value_changed(self, value: float): diff --git a/src/urh/plugins/InsertSine/insert_sine_dialog.ui b/src/urh/plugins/InsertSine/insert_sine_dialog.ui index 57505c1c..c68adae5 100644 --- a/src/urh/plugins/InsertSine/insert_sine_dialog.ui +++ b/src/urh/plugins/InsertSine/insert_sine_dialog.ui @@ -160,7 +160,11 @@
- + + + QPainter::Antialiasing|QPainter::HighQualityAntialiasing|QPainter::TextAntialiasing + + diff --git a/src/urh/ui/views/ZoomableGraphicView.py b/src/urh/ui/views/ZoomableGraphicView.py index cb281127..4ee073f3 100644 --- a/src/urh/ui/views/ZoomableGraphicView.py +++ b/src/urh/ui/views/ZoomableGraphicView.py @@ -1,4 +1,5 @@ from PyQt5.QtCore import pyqtSignal, QRectF +from PyQt5.QtGui import QResizeEvent from PyQt5.QtGui import QWheelEvent from PyQt5.QtWidgets import QGraphicsScene @@ -71,4 +72,8 @@ class ZoomableGraphicView(SelectableGraphicView): if self.scene_creator is not None: x1 = self.view_rect().x() x2 = x1 + self.view_rect().width() - self.scene_creator.show_scene_section(x1, x2) \ No newline at end of file + self.scene_creator.show_scene_section(x1, x2) + + def resizeEvent(self, event: QResizeEvent): + self.draw_full() + event.accept() \ No newline at end of file From 1d06785ed607061bbadcbcd5369e2638caf65191 Mon Sep 17 00:00:00 2001 From: jopohl Date: Sun, 5 Feb 2017 10:15:58 +0100 Subject: [PATCH 4/4] make signal insertable + set changed status after inserting data --- .../plugins/InsertSine/InsertSinePlugin.py | 17 +++++++- .../plugins/InsertSine/insert_sine_dialog.ui | 40 +++++++++++++------ src/urh/signalprocessing/Signal.py | 1 + src/urh/ui/views/EpicGraphicView.py | 6 +++ 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/urh/plugins/InsertSine/InsertSinePlugin.py b/src/urh/plugins/InsertSine/InsertSinePlugin.py index 34d020df..78a027cf 100644 --- a/src/urh/plugins/InsertSine/InsertSinePlugin.py +++ b/src/urh/plugins/InsertSine/InsertSinePlugin.py @@ -2,6 +2,7 @@ import os import numpy as np from PyQt5 import uic +from PyQt5.QtCore import pyqtSignal from PyQt5.QtCore import pyqtSlot from PyQt5.QtWidgets import QDialog @@ -10,11 +11,14 @@ from urh.util.Formatter import Formatter class InsertSinePlugin(SignalEditorPlugin): + insert_sine_wave_clicked = pyqtSignal() + def __init__(self): dirname = os.path.dirname(os.readlink(__file__)) if os.path.islink(__file__) else os.path.dirname(__file__) self.dialog_ui = uic.loadUi(os.path.realpath(os.path.join(dirname, "insert_sine_dialog.ui"))) # type: QDialog - self.amplitude = 1 + self.complex_wave = None + self.amplitude = 0.5 self.frequency = 10 self.phase = 0 self.sample_rate = 1e6 @@ -38,6 +42,8 @@ class InsertSinePlugin(SignalEditorPlugin): self.dialog_ui.doubleSpinBoxPhase.valueChanged.connect(self.on_double_spin_box_phase_value_changed) self.dialog_ui.doubleSpinBoxSampleRate.valueChanged.connect(self.on_double_spin_box_sample_rate_value_changed) self.dialog_ui.doubleSpinBoxNSamples.valueChanged.connect(self.on_spin_box_n_samples_value_changed) + self.dialog_ui.btnAbort.clicked.connect(self.on_btn_abort_clicked) + self.dialog_ui.btnOK.clicked.connect(self.on_btn_ok_clicked) def show_insert_sine_dialog(self): self.dialog_ui.show() @@ -79,3 +85,12 @@ class InsertSinePlugin(SignalEditorPlugin): self.num_samples = int(value) self.dialog_ui.labelTime.setText(Formatter.science_time(self.num_samples / self.sample_rate, decimals=3)) self.draw_sine_wave() + + @pyqtSlot() + def on_btn_abort_clicked(self): + self.dialog_ui.close() + + @pyqtSlot() + def on_btn_ok_clicked(self): + self.insert_sine_wave_clicked.emit() + self.dialog_ui.close() \ No newline at end of file diff --git a/src/urh/plugins/InsertSine/insert_sine_dialog.ui b/src/urh/plugins/InsertSine/insert_sine_dialog.ui index c68adae5..baf47e82 100644 --- a/src/urh/plugins/InsertSine/insert_sine_dialog.ui +++ b/src/urh/plugins/InsertSine/insert_sine_dialog.ui @@ -54,6 +54,19 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + @@ -131,19 +144,6 @@ - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -158,6 +158,20 @@ + + + + Ok + + + + + + + Abort + + + diff --git a/src/urh/signalprocessing/Signal.py b/src/urh/signalprocessing/Signal.py index 92738d15..83f9f8f6 100644 --- a/src/urh/signalprocessing/Signal.py +++ b/src/urh/signalprocessing/Signal.py @@ -378,3 +378,4 @@ class Signal(QObject): self._num_samples = len(self.data) self.data_edited.emit() self.protocol_needs_update.emit() + self.changed = True diff --git a/src/urh/ui/views/EpicGraphicView.py b/src/urh/ui/views/EpicGraphicView.py index cf4a1324..b35a94e6 100644 --- a/src/urh/ui/views/EpicGraphicView.py +++ b/src/urh/ui/views/EpicGraphicView.py @@ -73,7 +73,9 @@ class EpicGraphicView(SelectableGraphicView): font.setBold(True) self.insert_sine_action.setFont(font) self.insert_sine_action.triggered.connect(self.on_insert_sine_action_triggered) + self.insert_sine_plugin = InsertSinePlugin() + self.insert_sine_plugin.insert_sine_wave_clicked.connect(self.on_insert_sine_wave_clicked) @property @@ -373,3 +375,7 @@ class EpicGraphicView(SelectableGraphicView): def on_insert_sine_action_triggered(self): self.insert_sine_plugin.show_insert_sine_dialog() + @pyqtSlot() + def on_insert_sine_wave_clicked(self): + if self.insert_sine_plugin.complex_wave is not None: + self.signal.insert_data(self.paste_position, self.insert_sine_plugin.complex_wave)