Skip to content

Commit

Permalink
add spectrogram view and channel separation
Browse files Browse the repository at this point in the history
* show getting start hint in interpretation

* add test for creating spectrogram

* add spectrogram page to signal frame
remove parent frame property from epic graphic view

* prototype for spectrogram drawing (has performance issues)

* enable yscale for spectrogram

* regen file

* enhance spectrogram performance by using QImage

* enable y scale for spectrogram

* add initial legend to spectrogram

* fix colormap location

* remove colormap legend

* add more colormaps

* make colormap configurable via ini

* make colormap configurable in settings

* make fft window size configurable

* rescale Y on signal frame resize

* adapt unittest to new api

* allow y move with drag for spectrogram view

* refactor painting backend

* enable vertical selection in spectrogram graphic view

* spectrum: fix order of y values

* use fliplr for compat

* add bandpass filter function

* add narrowband iir filter

* set lower bandwidth for test

* add windowed sinc to filter class and adapt unittest

* change default of hold shift to drag
This way making a selection does not require a key modifier by default

* add fft convolution

* add performance test for fft convolution

* speed up performance test

* fix error for small data sets

* add test for filtering channels

* use astype for compatibility with old numpy versions

* refactor context menu of graphic views

* remove fft convolve performance test to avoid random fails on CI

* fix spectrogram calculation

* fix spectrogram calculation

* improve stft performance

* show samples in view for spectrogram and allow deeper zoom

* enable zoom to selection for spectrogram

* enable start end and selection infos for spectrogram selection

* enable bandpass filtering from spectrogram

* fix selection start end behavior for spectrogram

* update spectrogram infos in start end edited

* add unittest for channel separation

* enhance architecture of spectrogram management

* add class SpectrogramSceneManager
* cache spectrogram
* fix x axis orientation
* move scene managers to painting

* redraw on fft size update

* add lod slider for spectrogram

* remove unused stuff

* add tooltip for lod slider

* update selected bandwidth on sample rate changed

* add update for gv signal on resize

* fix fftshift parameter

* remove xflip as this is corrected by fftshift now

* remove lod slider as it leads to confusion and low lods are hard to see

* clip f_low and f_high

* update spectrogram images on colormap change

* set loading cursor right before bandpass filtering signal

* add select all action with ctrl+a to graphic views

* use parameters from original signal for newly created signals

* fix noise level in unittest

* improve spectrogram performance by splitting image into segments

* avoid division by zero

* fix unittest

* improve signal redraw on resize

* add created signal right under original signal

* adapt unittest to filtered frame created under original signal

* add dialog for configure bandwidth and display default values

* make bandwidth configurable

* fix spectrogram scene rect for small signals

* make data min and data max for spectrogram configurable

* use object names for indexing settings as texts are not reliable

Some OSes insert & before texts probably for shortcuts

* use heuristic to choose normal or FFT convolution

* suggest a filename for unsaved signals based on their name

* fix subpath range calculation

* use window for subpath drawing to avoid flickering colors
  • Loading branch information
jopohl authored Aug 24, 2017
1 parent fb00ca8 commit f75e578
Show file tree
Hide file tree
Showing 62 changed files with 8,991 additions and 2,182 deletions.
1,067 changes: 1,067 additions & 0 deletions src/urh/colormaps.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/urh/controller/ContinuousSendDialogController.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QCloseEvent

from urh.ContinuousSceneManager import ContinuousSceneManager
from urh.controller.SendDialogController import SendDialogController
from urh.controller.SendRecvDialogController import SendRecvDialogController
from urh.dev.VirtualDevice import VirtualDevice, Mode
from urh.signalprocessing.ContinuousModulator import ContinuousModulator
from urh.ui.painting.ContinuousSceneManager import ContinuousSceneManager


class ContinuousSendDialogController(SendDialogController):
Expand Down
5 changes: 2 additions & 3 deletions src/urh/controller/DecoderWidgetController.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@
QLineEdit, QMessageBox

from urh import constants
from urh.SignalSceneManager import SignalSceneManager
from urh.signalprocessing.Encoding import Encoding
from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer
from urh.signalprocessing.Signal import Signal
from urh.signalprocessing.Encoding import Encoding
from urh.ui.painting.SignalSceneManager import SignalSceneManager
from urh.ui.ui_decoding import Ui_Decoder
from urh.util import util
from urh.util.ProjectManager import ProjectManager


Expand Down
66 changes: 66 additions & 0 deletions src/urh/controller/FilterBandwidthDialogController.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QDialog, QLabel, QRadioButton

from urh import constants
from urh.signalprocessing.Filter import Filter
from urh.ui.ui_filter_bandwidth_dialog import Ui_DialogFilterBandwidth


class FilterBandwidthDialogController(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_DialogFilterBandwidth()
self.ui.setupUi(self)

bw_type = constants.SETTINGS.value("bandpass_filter_bw_type", "Medium", str)
custom_bw = constants.SETTINGS.value("bandpass_filter_custom_bw", 0.1, float)

for item in dir(self.ui):
item = getattr(self.ui, item)
if isinstance(item, QLabel):
name = item.objectName().replace("label", "")
key = next((key for key in Filter.BANDWIDTHS.keys() if name.startswith(key.replace(" ", ""))), None)
if key is not None and name.endswith("Bandwidth"):
item.setText("{0:n}".format(Filter.BANDWIDTHS[key]))
elif key is not None and name.endswith("KernelLength"):
item.setText(str(Filter.get_filter_length_from_bandwidth(Filter.BANDWIDTHS[key])))
elif isinstance(item, QRadioButton):
item.setChecked(bw_type.replace(" ", "_") == item.objectName().replace("radioButton", ""))

self.ui.doubleSpinBoxCustomBandwidth.setValue(custom_bw)
self.ui.spinBoxCustomKernelLength.setValue(Filter.get_filter_length_from_bandwidth(custom_bw))

self.create_connects()

def create_connects(self):
self.ui.doubleSpinBoxCustomBandwidth.valueChanged.connect(self.on_spin_box_custom_bandwidth_value_changed)
self.ui.spinBoxCustomKernelLength.valueChanged.connect(self.on_spin_box_custom_kernel_length_value_changed)
self.ui.buttonBox.accepted.connect(self.on_accepted)

@property
def checked_radiobutton(self):
for rb in dir(self.ui):
radio_button = getattr(self.ui, rb)
if isinstance(radio_button, QRadioButton) and radio_button.isChecked():
return radio_button
return None

@pyqtSlot(float)
def on_spin_box_custom_bandwidth_value_changed(self, bw: float):
self.ui.spinBoxCustomKernelLength.blockSignals(True)
self.ui.spinBoxCustomKernelLength.setValue(Filter.get_filter_length_from_bandwidth(bw))
self.ui.spinBoxCustomKernelLength.blockSignals(False)

@pyqtSlot(int)
def on_spin_box_custom_kernel_length_value_changed(self, filter_len: int):
self.ui.doubleSpinBoxCustomBandwidth.blockSignals(True)
self.ui.doubleSpinBoxCustomBandwidth.setValue(Filter.get_bandwidth_from_filter_length(filter_len))
self.ui.doubleSpinBoxCustomBandwidth.blockSignals(False)

@pyqtSlot()
def on_accepted(self):
if self.checked_radiobutton is not None:
bw_type = self.checked_radiobutton.objectName().replace("radioButton", "").replace("_", " ")
constants.SETTINGS.setValue("bandpass_filter_bw_type", bw_type)

constants.SETTINGS.setValue("bandpass_filter_custom_bw", self.ui.doubleSpinBoxCustomBandwidth.value())
19 changes: 14 additions & 5 deletions src/urh/controller/MainController.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def create_connects(self):
self.ui.fileTree.directory_open_wanted.connect(self.project_manager.set_project_folder)

self.signal_tab_controller.frame_closed.connect(self.close_signal_frame)
self.signal_tab_controller.signal_created.connect(self.add_signal)
self.signal_tab_controller.signal_created.connect(self.on_signal_created)
self.signal_tab_controller.ui.scrollArea.files_dropped.connect(self.on_files_dropped)
self.signal_tab_controller.files_dropped.connect(self.on_files_dropped)
self.signal_tab_controller.frame_was_dropped.connect(self.set_frame_numbers)
Expand Down Expand Up @@ -270,21 +270,23 @@ def add_signalfile(self, filename: str, group_id=0, enforce_sample_rate=None):
self.file_proxy_model.open_files.add(filename)
self.add_signal(signal, group_id)

def add_signal(self, signal, group_id=0):
def add_signal(self, signal, group_id=0, index=-1):
self.setCursor(Qt.WaitCursor)
pa = ProtocolAnalyzer(signal)
sig_frame = self.signal_tab_controller.add_signal_frame(pa)
sig_frame = self.signal_tab_controller.add_signal_frame(pa, index=index)
pa = self.compare_frame_controller.add_protocol(pa, group_id)

signal.blockSignals(True)
has_entry = self.project_manager.read_project_file_for_signal(signal)
if not has_entry:

if not has_entry and not signal.changed:
signal.auto_detect()

signal.blockSignals(False)

self.signal_protocol_dict[sig_frame] = pa

sig_frame.refresh(draw_full_signal=True) # Hier wird das Protokoll ausgelesen
sig_frame.refresh(draw_full_signal=True) # protocol is derived here
if self.project_manager.read_participants_for_signal(signal, pa.messages):
sig_frame.ui.gvSignal.redraw_view()

Expand Down Expand Up @@ -794,6 +796,9 @@ def on_options_changed(self, changed_options: dict):
if "default_view" in changed_options:
self.apply_default_view(int(changed_options["default_view"]))

if "spectrogram_colormap" in changed_options:
self.signal_tab_controller.redraw_spectrograms()

@pyqtSlot()
def on_text_edit_project_description_text_changed(self):
self.project_manager.description = self.ui.textEditProjectDescription.toPlainText()
Expand All @@ -805,3 +810,7 @@ def on_btn_file_tree_go_up_clicked(self):
path = cur_dir.path()
self.filemodel.setRootPath(path)
self.ui.fileTree.setRootIndex(self.file_proxy_model.mapFromSource(self.filemodel.index(path)))

@pyqtSlot(int, Signal)
def on_signal_created(self, index: int, signal: Signal):
self.add_signal(signal, index=index)
4 changes: 2 additions & 2 deletions src/urh/controller/ModulatorDialogController.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ def create_connects(self):
self.ui.gVModulated.zoomed.connect(self.on_carrier_data_modulated_zoomed)
self.ui.gVCarrier.zoomed.connect(self.on_carrier_data_modulated_zoomed)
self.ui.gVData.zoomed.connect(self.on_carrier_data_modulated_zoomed)
self.ui.gVModulated.sel_area_width_changed.connect(self.on_modulated_selection_changed)
self.ui.gVOriginalSignal.sel_area_width_changed.connect(self.on_original_selection_changed)
self.ui.gVModulated.selection_width_changed.connect(self.on_modulated_selection_changed)
self.ui.gVOriginalSignal.selection_width_changed.connect(self.on_original_selection_changed)
self.ui.spinBoxGaussBT.valueChanged.connect(self.on_gauss_bt_changed)
self.ui.spinBoxGaussFilterWidth.valueChanged.connect(self.on_gaus_filter_wdith_changed)

Expand Down
37 changes: 32 additions & 5 deletions src/urh/controller/OptionsController.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
import tempfile
from subprocess import call

from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QDialog, QHBoxLayout, QCompleter, QDirModel, QApplication, QHeaderView, QStyleFactory
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal, QSize
from PyQt5.QtGui import QCloseEvent, QIcon, QPixmap
from PyQt5.QtWidgets import QDialog, QHBoxLayout, QCompleter, QDirModel, QApplication, QHeaderView, QStyleFactory, \
QRadioButton

from urh import constants
from urh import constants, colormaps
from urh.controller.PluginController import PluginController
from urh.dev.BackendHandler import BackendHandler, Backends, BackendContainer
from urh.dev.native import ExtensionHelper
from urh.models.FieldTypeTableModel import FieldTypeTableModel
from urh.signalprocessing.FieldType import FieldType
from urh.signalprocessing.ProtocoLabel import ProtocolLabel
from urh.signalprocessing.Spectrogram import Spectrogram
from urh.ui.delegates.ComboBoxDelegate import ComboBoxDelegate
from urh.ui.ui_options import Ui_DialogOptions

Expand Down Expand Up @@ -43,7 +45,7 @@ def __init__(self, installed_plugins, highlighted_plugins=None, parent=None):
self.ui.comboBoxTheme.setCurrentIndex(constants.SETTINGS.value("theme_index", 0, int))
self.ui.checkBoxShowConfirmCloseDialog.setChecked(
not constants.SETTINGS.value('not_show_close_dialog', False, bool))
self.ui.checkBoxHoldShiftToDrag.setChecked(constants.SETTINGS.value('hold_shift_to_drag', False, bool))
self.ui.checkBoxHoldShiftToDrag.setChecked(constants.SETTINGS.value('hold_shift_to_drag', True, bool))
self.ui.checkBoxDefaultFuzzingPause.setChecked(
constants.SETTINGS.value('use_default_fuzzing_pause', True, bool))

Expand Down Expand Up @@ -91,6 +93,8 @@ def __init__(self, installed_plugins, highlighted_plugins=None, parent=None):
self.old_default_view = self.ui.comboBoxDefaultView.currentIndex()
self.ui.labelRebuildNativeStatus.setText("")

self.show_available_colormaps()

try:
self.restoreGeometry(constants.SETTINGS.value("{}/geometry".format(self.__class__.__name__)))
except TypeError:
Expand Down Expand Up @@ -201,6 +205,19 @@ def refresh_device_tab(self):
self.ui.lineEditGnuradioDirectory.setEnabled(self.backend_handler.use_gnuradio_install_dir)
self.ui.lineEditPython2Interpreter.setDisabled(self.backend_handler.use_gnuradio_install_dir)

def show_available_colormaps(self):
height = 50

selected = colormaps.read_selected_colormap_name_from_settings()
for colormap_name in sorted(colormaps.maps.keys()):
image = Spectrogram.create_colormap_image(colormap_name, height=height)
rb = QRadioButton(colormap_name)
rb.setObjectName(colormap_name)
rb.setChecked(colormap_name == selected)
rb.setIcon(QIcon(QPixmap.fromImage(image)))
rb.setIconSize(QSize(256, height))
self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().addWidget(rb)

def closeEvent(self, event: QCloseEvent):
changed_values = {}
if bool(self.ui.checkBoxPauseTime.isChecked()) != self.old_show_pause_as_time:
Expand All @@ -218,6 +235,16 @@ def closeEvent(self, event: QCloseEvent):
for plugin in self.plugin_controller.model.plugins:
plugin.destroy_settings_frame()

for i in range(self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().count()):
widget = self.ui.scrollAreaWidgetSpectrogramColormapContents.layout().itemAt(i).widget()
if isinstance(widget, QRadioButton) and widget.isChecked():
selected_colormap_name = widget.objectName()
if selected_colormap_name != colormaps.read_selected_colormap_name_from_settings():
colormaps.choose_colormap(selected_colormap_name)
colormaps.write_selected_colormap_to_settings(selected_colormap_name)
changed_values["spectrogram_colormap"] = selected_colormap_name
break

self.values_changed.emit(changed_values)

constants.SETTINGS.setValue("{}/geometry".format(self.__class__.__name__), self.saveGeometry())
Expand Down
4 changes: 2 additions & 2 deletions src/urh/controller/ProtocolSniffDialogController.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
from PyQt5.QtCore import pyqtSlot, pyqtSignal
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QCompleter, QDirModel
from urh.ui.painting.SniffSceneManager import SniffSceneManager

from urh import constants
from urh.SniffSceneManager import SniffSceneManager
from urh.LiveSceneManager import LiveSceneManager
from urh.controller.SendRecvDialogController import SendRecvDialogController
from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin
from urh.signalprocessing.ProtocolSniffer import ProtocolSniffer
from urh.ui.painting.LiveSceneManager import LiveSceneManager


class ProtocolSniffDialogController(SendRecvDialogController):
Expand Down
2 changes: 1 addition & 1 deletion src/urh/controller/ReceiveDialogController.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QMessageBox

from urh.LiveSceneManager import LiveSceneManager
from urh.controller.SendRecvDialogController import SendRecvDialogController
from urh.dev.VirtualDevice import Mode, VirtualDevice
from urh.ui.painting.LiveSceneManager import LiveSceneManager
from urh.util import FileOperator
from urh.util.Formatter import Formatter

Expand Down
7 changes: 2 additions & 5 deletions src/urh/controller/SendDialogController.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtGui import QBrush
from PyQt5.QtGui import QColor
from PyQt5.QtGui import QIcon
from PyQt5.QtGui import QPen
from PyQt5.QtGui import QBrush, QColor, QIcon, QPen
from PyQt5.QtWidgets import QMessageBox

from urh import constants
from urh.SignalSceneManager import SignalSceneManager
from urh.controller.SendRecvDialogController import SendRecvDialogController
from urh.dev.VirtualDevice import VirtualDevice, Mode
from urh.signalprocessing.Signal import Signal
from urh.ui.painting.SignalSceneManager import SignalSceneManager
from urh.util import FileOperator


Expand Down
Loading

0 comments on commit f75e578

Please sign in to comment.