mirror of
https://github.com/jopohl/urh.git
synced 2026-03-24 17:07:01 +01:00
2
setup.py
2
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:
|
||||
|
||||
96
src/urh/plugins/InsertSine/InsertSinePlugin.py
Normal file
96
src/urh/plugins/InsertSine/InsertSinePlugin.py
Normal file
@@ -0,0 +1,96 @@
|
||||
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
|
||||
|
||||
from urh.plugins.Plugin import SignalEditorPlugin
|
||||
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.complex_wave = None
|
||||
self.amplitude = 0.5
|
||||
self.frequency = 10
|
||||
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)
|
||||
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()
|
||||
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)
|
||||
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))
|
||||
self.dialog_ui.graphicsViewSineWave.draw_full()
|
||||
|
||||
@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()
|
||||
|
||||
@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()
|
||||
0
src/urh/plugins/InsertSine/__init__.py
Normal file
0
src/urh/plugins/InsertSine/__init__.py
Normal file
3
src/urh/plugins/InsertSine/descr.txt
Normal file
3
src/urh/plugins/InsertSine/descr.txt
Normal file
@@ -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!
|
||||
200
src/urh/plugins/InsertSine/insert_sine_dialog.ui
Normal file
200
src/urh/plugins/InsertSine/insert_sine_dialog.ui
Normal file
@@ -0,0 +1,200 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DialogCustomSine</class>
|
||||
<widget class="QDialog" name="DialogCustomSine">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>601</width>
|
||||
<height>308</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Insert sine wave</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Sample Rate:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="KillerDoubleSpinBox" name="doubleSpinBoxNSamples">
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>9999999999999999455752309870428160.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBoxPhase">
|
||||
<property name="suffix">
|
||||
<string>°</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>360.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBoxAmplitude">
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.001000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Frequency (Hz):</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="KillerDoubleSpinBox" name="doubleSpinBoxFrequency">
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>9999999999999999931398190359470212947659194368.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Phase:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="KillerDoubleSpinBox" name="doubleSpinBoxSampleRate">
|
||||
<property name="decimals">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>99999999999999991433150857216.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Samples:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Amplitude:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Time:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLabel" name="labelTime">
|
||||
<property name="text">
|
||||
<string>20s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QPushButton" name="btnOK">
|
||||
<property name="text">
|
||||
<string>Ok</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QPushButton" name="btnAbort">
|
||||
<property name="text">
|
||||
<string>Abort</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="ZoomableGraphicView" name="graphicsViewSineWave">
|
||||
<property name="renderHints">
|
||||
<set>QPainter::Antialiasing|QPainter::HighQualityAntialiasing|QPainter::TextAntialiasing</set>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ZoomableGraphicView</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>urh.ui.views.ZoomableGraphicView.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>KillerDoubleSpinBox</class>
|
||||
<extends>QDoubleSpinBox</extends>
|
||||
<header>urh.ui.KillerDoubleSpinBox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
47
src/urh/plugins/InsertSine/settings.ui
Normal file
47
src/urh/plugins/InsertSine/settings.ui
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>InsertSineWaveSettings</class>
|
||||
<widget class="QFrame" name="InsertSineWaveSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Frame</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>No settings available.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -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
|
||||
|
||||
@@ -70,4 +70,10 @@ class LabelAssignPlugin(Plugin):
|
||||
|
||||
class SDRPlugin(Plugin):
|
||||
def __init__(self, name: str):
|
||||
Plugin.__init__(self, name)
|
||||
Plugin.__init__(self, name)
|
||||
|
||||
|
||||
class SignalEditorPlugin(Plugin):
|
||||
def __init__(self, name: str):
|
||||
Plugin.__init__(self, name)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,16 @@ 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()
|
||||
self.insert_sine_plugin.insert_sine_wave_clicked.connect(self.on_insert_sine_wave_clicked)
|
||||
|
||||
|
||||
@property
|
||||
def signal(self):
|
||||
return self.parent_frame.signal
|
||||
@@ -118,6 +130,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 +144,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 +371,11 @@ 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()
|
||||
|
||||
@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)
|
||||
|
||||
@@ -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)
|
||||
self.scene_creator.show_scene_section(x1, x2)
|
||||
|
||||
def resizeEvent(self, event: QResizeEvent):
|
||||
self.draw_full()
|
||||
event.accept()
|
||||
@@ -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
|
||||
return default
|
||||
|
||||
Reference in New Issue
Block a user