diff --git a/data/ui/options.ui b/data/ui/options.ui index 90864ae1db..2a7df21e46 100644 --- a/data/ui/options.ui +++ b/data/ui/options.ui @@ -6,7 +6,7 @@ 0 0 - 803 + 814 822 @@ -21,58 +21,95 @@ - 1 + 0 Generation - - - - 20 - 20 - 324 - 102 - - - - - - - Samples - - - - - - - <html><head/><body><p>If you disable the default pause, the pause of the fuzzed message will be used.</p></body></html> - - - Use a default pause for fuzzed messages - - - - - - - 3 - - - 999999999.000000000000000 - - - - - - - Enable modulation profiles - - - - - + + + + + + + Samples + + + + + + + <html><head/><body><p>If you disable the default pause, the pause of the fuzzed message will be used.</p></body></html> + + + Use a default pause for fuzzed messages + + + + + + + 3 + + + 999999999.000000000000000 + + + + + + + Enable modulation profiles + + + + + + + + + Modulation Accuracy + + + + + + Low (2x8 bit) - Recommended for HackRF and RTL-SDR + + + + + + + Medium (2x16 bit) - Recommended for BladeRF, PlutoSDR and SDRPlay + + + + + + + High (2x32 bit) - Recommended if you are not sure what to choose + + + + + + + + + + Qt::Vertical + + + + 20 + 500 + + + + + @@ -244,8 +281,8 @@ 0 0 - 723 - 404 + 762 + 397 diff --git a/data/ui/send_recv_device_settings.ui b/data/ui/send_recv_device_settings.ui index 1ab6e2af7d..d43a792535 100644 --- a/data/ui/send_recv_device_settings.ui +++ b/data/ui/send_recv_device_settings.ui @@ -7,7 +7,7 @@ 0 0 860 - 668 + 711 @@ -501,6 +501,9 @@ QGroupBox::indicator:checked { Apply DC correction + + true + diff --git a/data/ui/send_recv_sniff_settings.ui b/data/ui/send_recv_sniff_settings.ui index 24b6a8df04..a83dbff16a 100644 --- a/data/ui/send_recv_sniff_settings.ui +++ b/data/ui/send_recv_sniff_settings.ui @@ -7,7 +7,7 @@ 0 0 482 - 388 + 424 diff --git a/data/ui/signal_frame.ui b/data/ui/signal_frame.ui index eb37061df3..a8c39f15f4 100644 --- a/data/ui/signal_frame.ui +++ b/data/ui/signal_frame.ui @@ -7,7 +7,7 @@ 0 0 1057 - 539 + 566 @@ -173,6 +173,9 @@ <html><head/><body><p>Set the <span style=" font-weight:600;">noise magnitude</span> of your signal. You can tune this value to mute noise in your signal and reveal the true data.</p></body></html> + + + 4 @@ -437,7 +440,7 @@ If you want your protocol to be better seperated, edit the PauseLen using right- - <html><head/><body><p>Choose the view of your signal. Analog, Demodulated or Spectrogram.</p><p>The quadrature demodulation uses a <span style=" font-weight:600;">treshold of magnitude,</span> to <span style=" font-weight:600;">supress noise</span>. All samples with a magnitude lower than this treshold will be eliminated (set to <span style=" font-style:italic;">-127</span>) after demod.</p><p>Tune this value by selecting a <span style=" font-style:italic;">noisy area</span> and mark it as noise using <span style=" font-weight:600;">context menu</span>.</p><p>Current noise treshold is: </p></body></html> + <html><head/><body><p>Choose the view of your signal. Analog, Demodulated or Spectrogram.</p><p>The quadrature demodulation uses a <span style=" font-weight:600;">threshold of magnitudes,</span> to <span style=" font-weight:600;">supress noise</span>. All samples with a magnitude lower than this treshold will be eliminated after demodulation.</p><p>Tune this value by selecting a <span style=" font-style:italic;">noisy area</span> and mark it as noise using <span style=" font-weight:600;">context menu</span>.</p></body></html> diff --git a/src/urh/ainterpretation/AutoInterpretation.py b/src/urh/ainterpretation/AutoInterpretation.py index 53b37b5dc3..4416100fbb 100644 --- a/src/urh/ainterpretation/AutoInterpretation.py +++ b/src/urh/ainterpretation/AutoInterpretation.py @@ -10,6 +10,7 @@ from urh.cythonext import auto_interpretation as c_auto_interpretation from urh.cythonext import signal_functions from urh.cythonext import util +from urh.signalprocessing.IQArray import IQArray def max_without_outliers(data: np.ndarray, z=3): @@ -59,7 +60,7 @@ def detect_noise_level(magnitudes): mean_values = np.fromiter((np.mean(chunk) for chunk in chunks), dtype=np.float32, count=len(chunks)) minimum, maximum = util.minmax(mean_values) - if minimum / maximum > 0.9: + if maximum == 0 or minimum / maximum > 0.9: # Mean values are very close to each other, so there is probably no noise in the signal return 0 @@ -175,10 +176,11 @@ def detect_modulation(data: np.ndarray, wavelet_scale=4, median_filter_order=11) return "OOK" -def detect_modulation_for_messages(signal: np.ndarray, message_indices: list) -> str: +def detect_modulation_for_messages(signal: IQArray, message_indices: list) -> str: modulations_for_messages = [] + complex = signal.as_complex64() for start, end in message_indices: - mod = detect_modulation(signal[start:end]) + mod = detect_modulation(complex[start:end]) if mod is not None: modulations_for_messages.append(mod) @@ -348,8 +350,11 @@ def get_bit_length_from_plateau_lengths(merged_plateau_lengths) -> int: return int(result) -def estimate(signal: np.ndarray, noise: float = None, modulation: str = None) -> dict: - magnitudes = np.abs(signal) +def estimate(iq_array: IQArray, noise: float = None, modulation: str = None) -> dict: + if isinstance(iq_array, np.ndarray): + iq_array = IQArray(iq_array) + + magnitudes = iq_array.magnitudes # find noise threshold noise = detect_noise_level(magnitudes) if noise is None else noise @@ -357,7 +362,7 @@ def estimate(signal: np.ndarray, noise: float = None, modulation: str = None) -> message_indices = segment_messages_from_magnitudes(magnitudes, noise_threshold=noise) # detect modulation - modulation = detect_modulation_for_messages(signal, message_indices) if modulation is None else modulation + modulation = detect_modulation_for_messages(iq_array, message_indices) if modulation is None else modulation if modulation is None: return None @@ -365,11 +370,11 @@ def estimate(signal: np.ndarray, noise: float = None, modulation: str = None) -> message_indices = merge_message_segments_for_ook(message_indices) if modulation == "OOK" or modulation == "ASK": - data = signal_functions.afp_demod(signal, noise, 0) + data = signal_functions.afp_demod(iq_array.data, noise, 0) elif modulation == "FSK": - data = signal_functions.afp_demod(signal, noise, 1) + data = signal_functions.afp_demod(iq_array.data, noise, 1) elif modulation == "PSK": - data = signal_functions.afp_demod(signal, noise, 2) + data = signal_functions.afp_demod(iq_array.data, noise, 2) else: raise ValueError("Unsupported Modulation") diff --git a/src/urh/cli/urh_cli.py b/src/urh/cli/urh_cli.py index 4e0367ca58..6034c37d2d 100755 --- a/src/urh/cli/urh_cli.py +++ b/src/urh/cli/urh_cli.py @@ -8,6 +8,8 @@ import numpy as np +from urh.signalprocessing.IQArray import IQArray + DEFAULT_CARRIER_FREQUENCY = 1e3 DEFAULT_CARRIER_AMPLITUDE = 1 DEFAULT_CARRIER_PHASE = 0 @@ -221,7 +223,7 @@ def modulate_messages(messages, modulator): cli_progress_bar(0, len(messages), title="Modulating") nsamples = sum(int(len(msg.encoded_bits) * modulator.samples_per_bit + msg.pause) for msg in messages) - buffer = np.zeros(nsamples, dtype=np.complex64) + buffer = IQArray(None, dtype=np.float32, n=nsamples) pos = 0 for i, msg in enumerate(messages): # We do not need to modulate the pause extra, as result is already initialized with zeros diff --git a/src/urh/controller/GeneratorTabController.py b/src/urh/controller/GeneratorTabController.py index 8449d332ca..2ce866ba7d 100644 --- a/src/urh/controller/GeneratorTabController.py +++ b/src/urh/controller/GeneratorTabController.py @@ -19,6 +19,7 @@ from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin from urh.plugins.PluginManager import PluginManager from urh.plugins.RfCat.RfCatPlugin import RfCatPlugin +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.Message import Message from urh.signalprocessing.MessageType import MessageType from urh.signalprocessing.Modulator import Modulator @@ -391,26 +392,29 @@ def generate_file(self): except Exception as e: logger.exception(e) sample_rate = 1e6 - FileOperator.save_data_dialog("generated.complex", modulated_samples, sample_rate=sample_rate, parent=self) + FileOperator.save_data_dialog("generated", modulated_samples, sample_rate=sample_rate, parent=self) except Exception as e: Errors.generic_error(self.tr("Failed to generate data"), str(e), traceback.format_exc()) self.unsetCursor() - def prepare_modulation_buffer(self, total_samples: int, show_error=True) -> np.ndarray: - memory_size_for_buffer = total_samples * 8 + def prepare_modulation_buffer(self, total_samples: int, show_error=True) -> IQArray: + dtype = Modulator.get_dtype() + n = 2 if dtype == np.int8 else 4 if dtype == np.int16 else 8 + + memory_size_for_buffer = total_samples * n logger.debug("Allocating {0:.2f}MB for modulated samples".format(memory_size_for_buffer / (1024 ** 2))) try: # allocate it three times as we need the same amount for the sending process - np.zeros(3*total_samples, dtype=np.complex64) + IQArray(None, dtype=dtype, n=3*total_samples) except MemoryError: # will go into continuous mode in this case if show_error: Errors.not_enough_ram_for_sending_precache(3*memory_size_for_buffer) return None - return np.zeros(total_samples, dtype=np.complex64) + return IQArray(None, dtype=dtype, n=total_samples) - def modulate_data(self, buffer: np.ndarray) -> np.ndarray: + def modulate_data(self, buffer: IQArray) -> IQArray: """ :param buffer: Buffer in which the modulated data shall be written, initialized with zeros diff --git a/src/urh/controller/dialogs/ModulatorDialog.py b/src/urh/controller/dialogs/ModulatorDialog.py index f03d7ea80d..e9faf89f4c 100644 --- a/src/urh/controller/dialogs/ModulatorDialog.py +++ b/src/urh/controller/dialogs/ModulatorDialog.py @@ -4,6 +4,7 @@ from PyQt5.QtWidgets import QDialog, QMessageBox from urh import constants +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.Modulator import Modulator from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer from urh.ui.ui_modulation import Ui_DialogModulation @@ -36,11 +37,16 @@ def __init__(self, modulators, tree_model=None, parent=None): self.modulators = modulators - for graphic_view in (self.ui.gVCarrier, self.ui.gVData, self.ui.gVModulated): + for graphic_view in (self.ui.gVCarrier, self.ui.gVData): graphic_view.scene_y_min = -1 graphic_view.scene_y_max = 1 graphic_view.scene_x_zoom_stretch = 1.1 + min_max_mod = IQArray.min_max_for_dtype(self.current_modulator.get_dtype()) + self.ui.gVModulated.scene_y_min = min_max_mod[0] + self.ui.gVModulated.scene_y_max = min_max_mod[1] + self.ui.gVModulated.scene_x_zoom_stretch = 1.1 + self.set_ui_for_current_modulator() self.ui.cbShowDataBitsOnly.setText(self.tr("Show Only Data Sequence\n")) diff --git a/src/urh/controller/dialogs/OptionsDialog.py b/src/urh/controller/dialogs/OptionsDialog.py index 9738c827bc..4fd14e1476 100644 --- a/src/urh/controller/dialogs/OptionsDialog.py +++ b/src/urh/controller/dialogs/OptionsDialog.py @@ -15,12 +15,13 @@ from urh.dev.native import ExtensionHelper from urh.models.FieldTypeTableModel import FieldTypeTableModel from urh.signalprocessing.FieldType import FieldType +from urh.signalprocessing.Modulator import Modulator 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 from urh.util import util - +import numpy as np class DeviceOptionsTableModel(QAbstractTableModel): header_labels = ["Software Defined Radio", "Info", "Native backend (recommended)", "GNU Radio backend"] @@ -194,6 +195,10 @@ def __init__(self, installed_plugins, highlighted_plugins=None, parent=None): self.ui.checkBoxMultipleModulations.setChecked(constants.SETTINGS.value("multiple_modulations", False, bool)) + self.ui.radioButtonLowModulationAccuracy.setChecked(Modulator.get_dtype() == np.int8) + self.ui.radioButtonMediumModulationAccuracy.setChecked(Modulator.get_dtype() == np.int16) + self.ui.radioButtonHighModulationAccuracy.setChecked(Modulator.get_dtype() == np.float32) + completer = QCompleter() completer.setModel(QDirModel(completer)) self.ui.lineEditPython2Interpreter.setCompleter(completer) @@ -242,6 +247,10 @@ def create_connects(self): self.ui.btnRemoveLabeltype.clicked.connect(self.on_btn_remove_label_type_clicked) self.ui.radioButtonPython2Interpreter.clicked.connect(self.on_radio_button_python2_interpreter_clicked) self.ui.radioButtonGnuradioDirectory.clicked.connect(self.on_radio_button_gnuradio_directory_clicked) + self.ui.radioButtonLowModulationAccuracy.clicked.connect(self.on_radio_button_low_modulation_accuracy_clicked) + self.ui.radioButtonMediumModulationAccuracy.clicked.connect(self.on_radio_button_medium_modulation_accuracy_clicked) + self.ui.radioButtonHighModulationAccuracy.clicked.connect(self.on_radio_button_high_modulation_accuracy_clicked) + self.ui.doubleSpinBoxRAMThreshold.valueChanged.connect(self.on_double_spinbox_ram_threshold_value_changed) self.ui.btnRebuildNative.clicked.connect(self.on_btn_rebuild_native_clicked) self.ui.comboBoxIconTheme.currentIndexChanged.connect(self.on_combobox_icon_theme_index_changed) @@ -508,6 +517,21 @@ def on_spin_box_font_size_editing_finished(self): font.setPointSize(self.ui.spinBoxFontSize.value()) qApp.setFont(font) + @pyqtSlot(bool) + def on_radio_button_high_modulation_accuracy_clicked(self, checked): + if checked: + constants.SETTINGS.setValue("modulation_dtype", "float32") + + @pyqtSlot(bool) + def on_radio_button_medium_modulation_accuracy_clicked(self, checked): + if checked: + constants.SETTINGS.setValue("modulation_dtype", "int16") + + @pyqtSlot(bool) + def on_radio_button_low_modulation_accuracy_clicked(self, checked): + if checked: + constants.SETTINGS.setValue("modulation_dtype", "int8") + @staticmethod def write_default_options(): settings = constants.SETTINGS diff --git a/src/urh/controller/dialogs/ProtocolSniffDialog.py b/src/urh/controller/dialogs/ProtocolSniffDialog.py index 429d93afa8..f0e6ca8942 100644 --- a/src/urh/controller/dialogs/ProtocolSniffDialog.py +++ b/src/urh/controller/dialogs/ProtocolSniffDialog.py @@ -42,10 +42,6 @@ def __init__(self, project_manager, signal=None, signals=None, parent=None, test self.create_connects() self.device_settings_widget.update_for_new_device(overwrite_settings=False) - - - - @property def view_type(self) -> int: return self.sniff_settings_widget.ui.comboBox_sniff_viewtype.currentIndex() @@ -73,7 +69,7 @@ def init_device(self): self.device = self.sniffer.rcv_device self._create_device_connects() - self.scene_manager = SniffSceneManager(np.array([]), parent=self) + self.scene_manager = SniffSceneManager(np.array([], dtype=self.device.data_type), parent=self) def emit_editing_finished_signals(self): super().emit_editing_finished_signals() diff --git a/src/urh/controller/dialogs/ReceiveDialog.py b/src/urh/controller/dialogs/ReceiveDialog.py index b8aa288973..f72ae942ed 100644 --- a/src/urh/controller/dialogs/ReceiveDialog.py +++ b/src/urh/controller/dialogs/ReceiveDialog.py @@ -66,7 +66,7 @@ def init_device(self): self.device = VirtualDevice(self.backend_handler, self.selected_device_name, Mode.receive, device_ip="192.168.10.2", parent=self) self._create_device_connects() - self.scene_manager = LiveSceneManager(np.array([]), parent=self) + self.scene_manager = LiveSceneManager(np.array([], dtype=self.device.data_type), parent=self) @pyqtSlot() def on_start_clicked(self): @@ -101,7 +101,7 @@ def on_save_clicked(self): initial_name = initial_name.replace(Formatter.local_decimal_seperator(), "_").replace("_000", "") - filename = FileOperator.save_data_dialog(initial_name + ".complex", data, + filename = FileOperator.save_data_dialog(initial_name, data, sample_rate=dev.sample_rate, parent=self) self.already_saved = True if filename is not None and filename not in self.recorded_files: diff --git a/src/urh/controller/dialogs/SendDialog.py b/src/urh/controller/dialogs/SendDialog.py index aa8fef369a..f139551810 100644 --- a/src/urh/controller/dialogs/SendDialog.py +++ b/src/urh/controller/dialogs/SendDialog.py @@ -5,6 +5,7 @@ from urh import constants from urh.controller.dialogs.SendRecvDialog import SendRecvDialog from urh.dev.VirtualDevice import VirtualDevice, Mode +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.Signal import Signal from urh.ui.painting.SignalSceneManager import SignalSceneManager from urh.util import FileOperator @@ -36,10 +37,12 @@ def __init__(self, project_manager, modulated_data, modulation_msg_indices=None, self.ui.labelCurrentMessage.hide() if modulated_data is not None: + assert isinstance(modulated_data, IQArray) # modulated_data is none in continuous send mode self.ui.progressBarSample.setMaximum(len(modulated_data)) samp_rate = self.device_settings_widget.ui.spinBoxSampleRate.value() - signal = Signal.from_samples(modulated_data, "Modulated Preview", samp_rate) + signal = Signal("", "Modulated Preview", sample_rate=samp_rate) + signal.iq_array = modulated_data self.scene_manager = SignalSceneManager(signal, parent=self) self.send_indicator = self.scene_manager.scene.addRect(0, -2, 0, 4, QPen(QColor(Qt.transparent), 0), @@ -83,7 +86,7 @@ def update_view(self): def init_device(self): device_name = self.selected_device_name num_repeats = self.device_settings_widget.ui.spinBoxNRepeat.value() - sts = self.scene_manager.signal._fulldata + sts = self.scene_manager.signal.iq_array self.device = VirtualDevice(self.backend_handler, device_name, Mode.send, samples_to_send=sts, device_ip="192.168.10.2", sending_repeats=num_repeats, parent=self) @@ -107,7 +110,7 @@ def on_graphics_view_save_as_clicked(self): def on_signal_data_edited(self): signal = self.scene_manager.signal self.ui.progressBarSample.setMaximum(signal.num_samples) - self.device.samples_to_send = signal.data + self.device.samples_to_send = signal.iq_array.data self.scene_manager.init_scene() self.ui.graphicsViewSend.redraw_view() diff --git a/src/urh/controller/dialogs/SimulatorDialog.py b/src/urh/controller/dialogs/SimulatorDialog.py index 72ef69a905..61774edc44 100644 --- a/src/urh/controller/dialogs/SimulatorDialog.py +++ b/src/urh/controller/dialogs/SimulatorDialog.py @@ -81,7 +81,7 @@ def __init__(self, simulator_config, modulators, sniffer = self.sniff_settings_widget.sniffer - self.scene_manager = SniffSceneManager(np.array([]), parent=self) + self.scene_manager = SniffSceneManager(np.array([], dtype=sniffer.rcv_device.data_type), parent=self) self.ui.graphicsViewPreview.setScene(self.scene_manager.scene) else: self.device_settings_rx_widget = self.sniff_settings_widget = self.scene_manager = None @@ -269,9 +269,9 @@ def on_simulation_started(self): else: self.ui.graphicsViewPreview.setEnabled(False) if self.ui.checkBoxCaptureFullRX.isChecked(): - self.scene_manager.plot_data = np.array([]) + self.scene_manager.plot_data = np.array([], dtype=rx_device.data_type) else: - self.scene_manager.data_array = np.array([]) + self.scene_manager.data_array = np.array([], dtype=rx_device.data_type) self.scene_manager.scene.addText("Could not generate RX preview.") @pyqtSlot() @@ -355,6 +355,7 @@ def on_selected_rx_device_changed(self): dev_name = self.device_settings_rx_widget.ui.cbDevice.currentText() self.simulator.sniffer.device_name = dev_name self.device_settings_rx_widget.device = self.simulator.sniffer.rcv_device + self.__set_rx_scene() @pyqtSlot() def on_selected_tx_device_changed(self): @@ -392,17 +393,25 @@ def on_radio_button_transcript_hex_clicked(self): def on_radio_button_transcript_bit_clicked(self): self.update_transcript_view() - @pyqtSlot() - def on_checkbox_capture_full_rx_clicked(self): - self.simulator.sniffer.rcv_device.resume_on_full_receive_buffer = not self.ui.checkBoxCaptureFullRX.isChecked() + def __set_rx_scene(self): + if not self.rx_needed: + return + if self.ui.checkBoxCaptureFullRX.isChecked(): - self.scene_manager = LiveSceneManager(np.array([]), parent=self) + self.scene_manager = LiveSceneManager(np.array([], dtype=self.simulator.sniffer.rcv_device.data_type), + parent=self) self.ui.graphicsViewPreview.setScene(self.scene_manager.scene) else: - self.scene_manager = SniffSceneManager(np.array([]), parent=self) + self.scene_manager = SniffSceneManager(np.array([], dtype=self.simulator.sniffer.rcv_device.data_type), + parent=self) self.ui.graphicsViewPreview.setScene(self.scene_manager.scene) + @pyqtSlot() + def on_checkbox_capture_full_rx_clicked(self): + self.simulator.sniffer.rcv_device.resume_on_full_receive_buffer = not self.ui.checkBoxCaptureFullRX.isChecked() + self.__set_rx_scene() + @pyqtSlot() def on_btn_save_rx_clicked(self): rx_device = self.simulator.sniffer.rcv_device diff --git a/src/urh/controller/widgets/DeviceSettingsWidget.py b/src/urh/controller/widgets/DeviceSettingsWidget.py index 15e08f7ac7..c46d2fdfb2 100644 --- a/src/urh/controller/widgets/DeviceSettingsWidget.py +++ b/src/urh/controller/widgets/DeviceSettingsWidget.py @@ -12,7 +12,7 @@ from urh.plugins.PluginManager import PluginManager from urh.ui.ui_send_recv_device_settings import Ui_FormDeviceSettings from urh.util.ProjectManager import ProjectManager - +import numpy as np class DeviceSettingsWidget(QWidget): selected_device_changed = pyqtSignal() @@ -99,7 +99,7 @@ def set_val(ui_widget, key: str, default): self.set_default_bb_gain() if self.is_rx: - checked = conf_dict.get("apply_dc_correction", False) + checked = conf_dict.get("apply_dc_correction", True) if isinstance(checked, str): checked = True if checked == "True" else False self.ui.checkBoxDCCorrection.setChecked(checked) @@ -171,7 +171,7 @@ def set_default_rf_gain(self): prefix = self.rx_tx_prefix if prefix + "rf_gain" in conf: key = prefix + "rf_gain" - gain = conf[key][int(median(range(len(conf[key]))))] + gain = conf[key][int(np.percentile(range(len(conf[key])), 25))] self.ui.spinBoxGain.setValue(gain) def set_default_if_gain(self): @@ -187,7 +187,7 @@ def set_default_bb_gain(self): prefix = self.rx_tx_prefix if prefix + "baseband_gain" in conf: key = prefix + "baseband_gain" - baseband_gain = conf[key][int(median(range(len(conf[key]))))] + baseband_gain = conf[key][int(np.percentile(list(range(len(conf[key]))), 25))] self.ui.spinBoxBasebandGain.setValue(baseband_gain) def sync_gain_sliders(self): diff --git a/src/urh/controller/widgets/SignalFrame.py b/src/urh/controller/widgets/SignalFrame.py index db1c6bf5ab..29354aebdd 100644 --- a/src/urh/controller/widgets/SignalFrame.py +++ b/src/urh/controller/widgets/SignalFrame.py @@ -1,5 +1,6 @@ import math import time +import traceback from multiprocessing import Process, Array import numpy as np @@ -14,6 +15,7 @@ from urh.controller.dialogs.SendDialog import SendDialog from urh.controller.dialogs.SignalDetailsDialog import SignalDetailsDialog from urh.signalprocessing.Filter import Filter, FilterType +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer from urh.signalprocessing.Signal import Signal from urh.signalprocessing.Spectrogram import Spectrogram @@ -142,7 +144,6 @@ def __init__(self, proto_analyzer: ProtocolAnalyzer, undo_stack: QUndoStack, pro self.set_protocol_visibility() self.ui.chkBoxShowProtocol.setChecked(True) - self.set_qad_tooltip(self.signal.noise_threshold) self.ui.btnSaveSignal.hide() self.show_protocol(refresh=False) @@ -250,7 +251,7 @@ def refresh_signal_information(self, block=True): self.ui.spinBoxTolerance.setValue(self.signal.tolerance) self.ui.spinBoxCenterOffset.setValue(self.signal.qad_center) self.ui.spinBoxInfoLen.setValue(self.signal.bit_len) - self.ui.spinBoxNoiseTreshold.setValue(self.signal.noise_threshold) + self.ui.spinBoxNoiseTreshold.setValue(self.signal.noise_threshold_relative) self.ui.cbModulationType.setCurrentIndex(self.signal.modulation_type) self.ui.btnAdvancedModulationSettings.setVisible(self.ui.cbModulationType.currentText() == "ASK") @@ -286,7 +287,7 @@ def update_number_selected_samples(self): if start < end: max_window_size = 10 ** 5 step_size = int(math.ceil((end - start) / max_window_size)) - power = np.mean(np.abs(self.signal.data[start:end:step_size])) + power = np.mean(self.signal.iq_array.subarray(start,end,step_size).magnitudes_normalized) if power > 0: power_str = Formatter.big_value_with_suffix(10 * np.log10(power), 2) @@ -415,17 +416,10 @@ def save_signal(self): self.save_signal_as() def save_signal_as(self): - if self.signal.filename: - initial_name = self.signal.filename - else: - initial_name = self.signal.name.replace(" ", "-").replace(",", ".").replace(".", "_") + ".complex" - - filename = FileOperator.get_save_file_name(initial_name, wav_only=self.signal.wav_mode) - if filename: - try: - self.signal.save_as(filename) - except Exception as e: - QMessageBox.critical(self, self.tr("Error saving signal"), e.args[0]) + try: + FileOperator.save_data_dialog(self.signal.name, self.signal.iq_array, self.signal.sample_rate, self.signal.wav_mode) + except Exception as e: + Errors.generic_error("Error saving file", str(e), traceback.format_exc()) def export_demodulated(self): try: @@ -442,8 +436,7 @@ def export_demodulated(self): if filename.endswith(".wav"): data = self.signal.qad.astype(np.float32) data /= np.max(np.abs(data)) - data = FileOperator.convert_data_to_format(data, filename) - FileOperator.save_data(data, filename, self.signal.sample_rate, num_channels=1) + FileOperator.save_data(IQArray(data, skip_conversion=True), filename, self.signal.sample_rate, num_channels=1) self.unsetCursor() except Exception as e: QMessageBox.critical(self, self.tr("Error exporting demodulated data"), e.args[0]) @@ -574,7 +567,8 @@ def draw_spectrogram(self, show_full_scene=False, force_redraw=False): window_size = 2 ** self.ui.sliderFFTWindowSize.value() data_min, data_max = self.ui.sliderSpectrogramMin.value(), self.ui.sliderSpectrogramMax.value() - redraw_needed = self.ui.gvSpectrogram.scene_manager.set_parameters(self.signal.data, window_size=window_size, + redraw_needed = self.ui.gvSpectrogram.scene_manager.set_parameters(self.signal.iq_array.data, + window_size=window_size, data_min=data_min, data_max=data_max) self.ui.gvSpectrogram.scene_manager.update_scene_rect() @@ -646,14 +640,14 @@ def on_set_noise_in_graphic_view_clicked(self): start = self.ui.gvSignal.selection_area.x end = start + self.ui.gvSignal.selection_area.width - new_thresh = self.signal.calc_noise_threshold(start, end) + new_thresh = self.signal.calc_relative_noise_threshold_from_range(start, end) self.ui.spinBoxNoiseTreshold.setValue(new_thresh) self.ui.spinBoxNoiseTreshold.editingFinished.emit() self.unsetCursor() @pyqtSlot() def on_noise_threshold_changed(self): - self.ui.spinBoxNoiseTreshold.setValue(self.signal.noise_threshold) + self.ui.spinBoxNoiseTreshold.setValue(self.signal.noise_threshold_relative) minimum = self.signal.noise_min_plot maximum = self.signal.noise_max_plot if self.ui.cbSignalView.currentIndex() == 0: @@ -765,7 +759,7 @@ def on_btn_autodetect_clicked(self): def on_btn_replay_clicked(self): project_manager = self.project_manager try: - dialog = SendDialog(project_manager, modulated_data=self.signal.data, parent=self) + dialog = SendDialog(project_manager, modulated_data=self.signal.iq_array, parent=self) except OSError as e: logger.error(repr(e)) return @@ -994,10 +988,7 @@ def refresh_signal(self, draw_full_signal=False): self.draw_signal(draw_full_signal) self.__set_samples_in_view() - self.update_number_selected_samples() - - self.set_qad_tooltip(self.signal.noise_threshold) self.on_slider_y_scale_value_changed() @pyqtSlot(float) @@ -1012,21 +1003,12 @@ def on_signal_qad_center_changed(self, qad_center): self.ui.spinBoxCenterOffset.setValue(qad_center) def on_spinbox_noise_threshold_editing_finished(self): - if self.signal is not None and self.signal.noise_threshold != self.ui.spinBoxNoiseTreshold.value(): + if self.signal is not None and self.signal.noise_threshold_relative != self.ui.spinBoxNoiseTreshold.value(): noise_action = ChangeSignalParameter(signal=self.signal, protocol=self.proto_analyzer, - parameter_name="noise_threshold", + parameter_name="noise_threshold_relative", parameter_value=self.ui.spinBoxNoiseTreshold.value()) self.undo_stack.push(noise_action) - def set_qad_tooltip(self, noise_threshold): - self.ui.cbSignalView.setToolTip( - "

Choose the view of your signal: Analog, Demodulated or Spectrogram.

" - "

The quadrature demodulation uses a threshold of magnitude, to supress noise. " - "All samples with a magnitude lower than this threshold will be eliminated " - "(set to -127) after demodulation.

" - "

Tune this value by selecting a noisy area and mark it as noise using context menu.

" - "

Current noise threshold is: " + str(noise_threshold) + "

") - def contextMenuEvent(self, event: QContextMenuEvent): if self.signal is None: return @@ -1205,7 +1187,7 @@ def on_bandpass_filter_triggered(self, f_low: float, f_high: float): QApplication.instance().setOverrideCursor(Qt.WaitCursor) filter_bw = Filter.read_configured_filter_bw() filtered = Array("f", 2 * self.signal.num_samples) - p = Process(target=perform_filter, args=(filtered, self.signal.data, f_low, f_high, filter_bw)) + p = Process(target=perform_filter, args=(filtered, self.signal.iq_array.as_complex64(), f_low, f_high, filter_bw)) p.daemon = True p.start() @@ -1282,6 +1264,6 @@ def on_export_fta_wanted(self): include_amplitude=filename.endswith(".fta")) except Exception as e: logger.exception(e) - Errors.generic_error("Failed to export spectrogram", str(e)) + Errors.generic_error("Failed to export spectrogram", str(e), traceback.format_exc()) finally: QApplication.restoreOverrideCursor() diff --git a/src/urh/controller/widgets/SniffSettingsWidget.py b/src/urh/controller/widgets/SniffSettingsWidget.py index 3b14aecd20..aeff74714d 100644 --- a/src/urh/controller/widgets/SniffSettingsWidget.py +++ b/src/urh/controller/widgets/SniffSettingsWidget.py @@ -76,7 +76,7 @@ def set_val(widget, key: str, default): set_val(self.ui.spinbox_sniff_BitLen, "bit_len", signal.bit_len if signal else 100) set_val(self.ui.spinbox_sniff_Center, "center", signal.qad_center if signal else 0.02) set_val(self.ui.spinbox_sniff_ErrorTolerance, "tolerance", signal.tolerance if signal else 5) - set_val(self.ui.spinbox_sniff_Noise, "noise", signal.noise_threshold if signal else 0.001) + set_val(self.ui.spinbox_sniff_Noise, "noise", signal.noise_threshold_relative if signal else 0.001) set_val(self.ui.combox_sniff_Modulation, "modulation_index", signal.modulation_type if signal else 1) self.ui.comboBox_sniff_encoding.setCurrentText(conf_dict.get("decoding_name", "")) self.ui.checkBoxAdaptiveNoise.setChecked(bool(conf_dict.get("adaptive_noise", False))) @@ -119,7 +119,7 @@ def emit_sniff_parameters_changed(self): @pyqtSlot() def on_noise_edited(self): - self.sniffer.signal._noise_threshold = self.ui.spinbox_sniff_Noise.value() + self.sniffer.signal.noise_threshold_relative = self.ui.spinbox_sniff_Noise.value() self.sniff_setting_edited.emit() @pyqtSlot() @@ -184,7 +184,7 @@ def on_btn_sniff_use_signal_clicked(self): self.ui.spinbox_sniff_BitLen.setValue(signal.bit_len) self.ui.spinbox_sniff_Center.setValue(signal.qad_center) - self.ui.spinbox_sniff_Noise.setValue(signal.noise_threshold) + self.ui.spinbox_sniff_Noise.setValue(signal.noise_threshold_relative) self.ui.spinbox_sniff_ErrorTolerance.setValue(signal.tolerance) self.ui.combox_sniff_Modulation.setCurrentIndex(signal.modulation_type) diff --git a/src/urh/cythonext/auto_interpretation.pyx b/src/urh/cythonext/auto_interpretation.pyx index 10b707dfd8..7836072035 100644 --- a/src/urh/cythonext/auto_interpretation.pyx +++ b/src/urh/cythonext/auto_interpretation.pyx @@ -3,6 +3,7 @@ cimport numpy as np import numpy as np from cpython cimport array import array +import cython cpdef tuple k_means(float[:] data, unsigned int k=2): cdef float[:] centers = np.empty(k, dtype=np.float32) @@ -46,7 +47,7 @@ cpdef tuple k_means(float[:] data, unsigned int k=2): return centers, clusters -def segment_messages_from_magnitudes(float[:] magnitudes, float noise_threshold): +def segment_messages_from_magnitudes(cython.floating[:] magnitudes, float noise_threshold): """ Get the list of start, end indices of messages diff --git a/src/urh/cythonext/path_creator.pyx b/src/urh/cythonext/path_creator.pyx index 5098da08f7..4f936f8181 100644 --- a/src/urh/cythonext/path_creator.pyx +++ b/src/urh/cythonext/path_creator.pyx @@ -10,18 +10,25 @@ from PyQt5.QtGui import QPainterPath # np.import_array() from cython.parallel import prange +from urh.cythonext.util cimport iq from urh import constants import math +import cython -cpdef create_path(float[:] samples, long long start, long long end, list subpath_ranges=None): - cdef float[:] values +cpdef create_path(iq[:] samples, long long start, long long end, list subpath_ranges=None): + cdef iq[:] values cdef long long[::1] sample_rng cdef np.int64_t[::1] x - cdef float sample, minimum, maximum, tmp, scale_factor + cdef iq sample, minimum, maximum, tmp + cdef float scale_factor cdef long long i,j,index, chunk_end, num_samples, pixels_on_path, samples_per_pixel num_samples = end - start + cdef dict type_lookup = {"char[:]": np.int8, "unsigned char[:]": np.uint8, + "short[:]": np.int16, "unsigned short[:]": np.uint16, + "float[:]": np.float32, "double[:]": np.float64} + subpath_ranges = [(start, end)] if subpath_ranges is None else subpath_ranges pixels_on_path = constants.PIXELS_PER_PATH @@ -33,7 +40,7 @@ cpdef create_path(float[:] samples, long long start, long long end, list subpath if samples_per_pixel > 1: sample_rng = np.arange(start, end, samples_per_pixel, dtype=np.int64) - values = np.zeros(2 * len(sample_rng), dtype=np.float32, order="C") + values = np.zeros(2 * len(sample_rng), dtype=type_lookup[cython.typeof(samples)], order="C") scale_factor = num_samples / (2.0 * len(sample_rng)) # 2.0 is important to make it a float division! for i in prange(start, end, samples_per_pixel, nogil=True, schedule='static', num_threads=num_threads): chunk_end = i + samples_per_pixel @@ -75,10 +82,10 @@ cpdef create_path(float[:] samples, long long start, long long end, list subpath return result -cpdef create_live_path(float[:] samples, unsigned int start, unsigned int end): +cpdef create_live_path(iq[:] samples, unsigned int start, unsigned int end): return array_to_QPath(np.arange(start, end).astype(np.int64), samples) -cpdef array_to_QPath(np.int64_t[:] x, float[:] y): +cpdef array_to_QPath(np.int64_t[:] x, y): """ Convert an array of x,y coordinates to QPainterPath as efficiently as possible. diff --git a/src/urh/cythonext/signal_functions.pyx b/src/urh/cythonext/signal_functions.pyx index c1d59c137b..38e0d0327f 100644 --- a/src/urh/cythonext/signal_functions.pyx +++ b/src/urh/cythonext/signal_functions.pyx @@ -4,10 +4,14 @@ import cython import numpy as np from libcpp cimport bool -from urh.cythonext import util +from urh.cythonext.util cimport IQ, iq from cython.parallel import prange -from libc.math cimport atan2, sqrt, M_PI, sin, cos +from libc.math cimport atan2, sqrt, M_PI + +cdef extern from "math.h" nogil: + float cosf(float x) + float sinf(float x) # As we do not use any numpy C API functions we do no import_array here, # because it can lead to OS X error: https://github.com/jopohl/urh/issues/273 @@ -29,16 +33,25 @@ cpdef float get_noise_for_mod_type(int mod_type): else: return 0 -cpdef np.ndarray[np.complex64_t, ndim=1] modulate_fsk(unsigned char[:] bit_array, - unsigned long pause, unsigned long start, - float a, float freq0, float freq1, - float phi, float sample_rate, - long long samples_per_bit): +cdef get_numpy_dtype(iq cython_type): + if str(cython.typeof(cython_type)) == "char": + return np.int8 + elif str(cython.typeof(cython_type)) == "short": + return np.int16 + elif str(cython.typeof(cython_type)) == "float": + return np.float32 + else: + raise ValueError("dtype {} not supported for modulation".format(cython.typeof(cython_type))) + +cpdef modulate_fsk(unsigned char[:] bit_array, unsigned long pause, unsigned long start, + float a, float freq0, float freq1, float phi, float sample_rate, + long long samples_per_bit, iq iq_type): cdef long long i = 0, j = 0, index = 0 cdef float t = 0, f = 0, arg = 0, f_next = 0, phase = 0 cdef long long total_samples = int(len(bit_array) * samples_per_bit + pause) - cdef np.ndarray[np.complex64_t, ndim=1] result = np.zeros(total_samples, dtype=np.complex64) + result = np.zeros((total_samples, 2), dtype=get_numpy_dtype(iq_type)) + cdef iq[:, ::1] result_view = result cdef float* phases = malloc(total_samples * sizeof(float)) for i in range(0, samples_per_bit): @@ -67,7 +80,8 @@ cpdef np.ndarray[np.complex64_t, ndim=1] modulate_fsk(unsigned char[:] bit_array f = freq0 if bit_array[index] == 0 else freq1 arg = 2 * M_PI * f * t + phases[i] - result[i] = a*(cos(arg) + imag_unit * sin(arg)) + result_view[i, 0] = (a * cosf(arg)) + result_view[i, 1] = (a * sinf(arg)) # We need to correct the phase on transitions between 0 and 1 # if i < loop_end - 1 and (i+1) % samples_per_bit == 0: @@ -79,16 +93,15 @@ cpdef np.ndarray[np.complex64_t, ndim=1] modulate_fsk(unsigned char[:] bit_array free(phases) return result -cpdef np.ndarray[np.complex64_t, ndim=1] modulate_ask(unsigned char[:] bit_array, - unsigned long pause, unsigned long start, - double a0, double a1, double f, - double phi, double sample_rate, - unsigned long samples_per_bit): +cpdef modulate_ask(unsigned char[:] bit_array, unsigned long pause, unsigned long start, + double a0, double a1, double f, double phi, double sample_rate, + unsigned long samples_per_bit, iq iq_type): cdef long long i = 0, index = 0 cdef float t = 0, a = 0, arg = 0 cdef long long total_samples = int(len(bit_array) * samples_per_bit + pause) - cdef np.ndarray[np.complex64_t, ndim=1] result = np.zeros(total_samples, dtype=np.complex64) + result = np.zeros((total_samples, 2), dtype=get_numpy_dtype(iq_type)) + cdef iq[:, ::1] result_view = result cdef long long loop_end = total_samples-pause for i in prange(0, loop_end, nogil=True, schedule="static"): @@ -98,20 +111,20 @@ cpdef np.ndarray[np.complex64_t, ndim=1] modulate_ask(unsigned char[:] bit_array if a > 0: t = (i+start) / sample_rate arg = 2 * M_PI * f * t + phi - result[i] = a*(cos(arg) + imag_unit * sin(arg)) + result_view[i, 0] = (a * cosf(arg)) + result_view[i, 1] = (a * sinf(arg)) return result -cpdef np.ndarray[np.complex64_t, ndim=1] modulate_psk(unsigned char[:] bit_array, - unsigned long pause, unsigned long start, - double a, double f, - double phi0, double phi1, double sample_rate, - unsigned long samples_per_bit): +cpdef modulate_psk(unsigned char[:] bit_array, unsigned long pause, unsigned long start, + double a, double f, double phi0, double phi1, double sample_rate, + unsigned long samples_per_bit, iq iq_type): cdef long long i = 0, index = 0 cdef float t = 0, phi = 0, arg = 0 cdef long long total_samples = int(len(bit_array) * samples_per_bit + pause) - cdef np.ndarray[np.complex64_t, ndim=1] result = np.zeros(total_samples, dtype=np.complex64) + result = np.zeros((total_samples, 2), dtype=get_numpy_dtype(iq_type)) + cdef iq[:, ::1] result_view = result cdef long long loop_end = total_samples-pause for i in prange(0, loop_end, nogil=True, schedule="static"): @@ -120,13 +133,14 @@ cpdef np.ndarray[np.complex64_t, ndim=1] modulate_psk(unsigned char[:] bit_array t = (i+start) / sample_rate arg = 2 * M_PI * f * t + phi - result[i] = a*(cos(arg) + imag_unit * sin(arg)) + result_view[i, 0] = (a * cosf(arg)) + result_view[i, 1] = (a * sinf(arg)) return result -cdef np.ndarray[np.float64_t, ndim=1] gauss_fir(double sample_rate, unsigned long samples_per_bit, - double bt=.5, double filter_width=1.0): +cdef np.ndarray[np.float32_t, ndim=1] gauss_fir(float sample_rate, unsigned long samples_per_bit, + float bt=.5, float filter_width=1.0): """ :param filter_width: Filter width @@ -134,30 +148,29 @@ cdef np.ndarray[np.float64_t, ndim=1] gauss_fir(double sample_rate, unsigned lon :return: """ # http://onlinelibrary.wiley.com/doi/10.1002/9780470041956.app2/pdf - k = np.arange(-int(filter_width * samples_per_bit), int(filter_width * samples_per_bit) + 1) - ts = samples_per_bit / sample_rate # symbol time - h = np.sqrt((2 * np.pi) / (np.log(2))) * bt / ts * np.exp( + cdef np.ndarray[np.float32_t] k = np.arange(-int(filter_width * samples_per_bit), + int(filter_width * samples_per_bit) + 1, + dtype=np.float32) + cdef float ts = samples_per_bit / sample_rate # symbol time + cdef np.ndarray[np.float32_t] h = np.sqrt((2 * np.pi) / (np.log(2))) * bt / ts * np.exp( -(((np.sqrt(2) * np.pi) / np.sqrt(np.log(2)) * bt * k / samples_per_bit) ** 2)) return h / h.sum() -cpdef np.ndarray[np.complex64_t, ndim=1] modulate_gfsk(unsigned char[:] bit_array, - unsigned long pause, unsigned long start, - double a, double freq0, double freq1, - double phi, double sample_rate, - unsigned long samples_per_bit, - double gauss_bt, double filter_width): +cpdef modulate_gfsk(unsigned char[:] bit_array, unsigned long pause, unsigned long start, + double a, double freq0, double freq1, double phi, double sample_rate, + unsigned long samples_per_bit, double gauss_bt, double filter_width, iq iq_type): cdef long long i = 0, index = 0 cdef long long total_samples = int(len(bit_array) * samples_per_bit + pause) - cdef np.ndarray[np.float64_t, ndim=1] frequencies = np.empty(total_samples - pause, dtype=np.float64) + cdef np.ndarray[np.float32_t, ndim=1] frequencies = np.empty(total_samples - pause, dtype=np.float32) cdef long long loop_end = total_samples-pause for i in prange(0, loop_end, nogil=True, schedule="static"): index = (i/samples_per_bit) frequencies[i] = freq0 if bit_array[index] == 0 else freq1 - cdef np.ndarray[np.float64_t, ndim=1] t = np.arange(start, start + total_samples - pause) / sample_rate - cdef np.ndarray[np.float64_t, ndim=1] gfir = gauss_fir(sample_rate, samples_per_bit, + cdef np.ndarray[np.float32_t, ndim=1] t = np.arange(start, start + total_samples - pause, dtype=np.float32) / sample_rate + cdef np.ndarray[np.float32_t, ndim=1] gfir = gauss_fir(sample_rate, samples_per_bit, bt=gauss_bt, filter_width=filter_width) if len(frequencies) >= len(gfir): @@ -166,18 +179,21 @@ cpdef np.ndarray[np.complex64_t, ndim=1] modulate_gfsk(unsigned char[:] bit_arra # Prevent dimension crash later, because gaussian finite impulse response is longer then param_vector frequencies = np.convolve(gfir, frequencies, mode="same")[:len(frequencies)] - cdef np.ndarray[np.complex64_t, ndim=1] result = np.zeros(total_samples, dtype=np.complex64) + result = np.zeros((total_samples, 2), dtype=get_numpy_dtype(iq_type)) + cdef iq[:, ::1] result_view = result - cdef np.ndarray[np.float64_t, ndim=1] phases = np.empty(len(frequencies), dtype=np.float64) + cdef np.ndarray[np.float32_t, ndim=1] phases = np.empty(len(frequencies), dtype=np.float32) phases[0] = phi for i in range(0, len(phases) - 1): # Correct the phase to prevent spiky jumps phases[i + 1] = 2 * M_PI * t[i] * (frequencies[i] - frequencies[i + 1]) + phases[i] - cdef np.ndarray[np.float64_t, ndim=1] arg = (2 * M_PI * frequencies * t + phases) - result[:total_samples - pause].real = a * np.cos(arg) - result[:total_samples - pause].imag = a * np.sin(arg) + cdef np.ndarray[np.float32_t, ndim=1] arg = (2 * M_PI * frequencies * t + phases) + cdef long long stop = max(0, total_samples-pause) + for i in prange(0, stop, nogil=True, schedule="static"): + result_view[i, 0] = (a * cosf(arg[i])) + result_view[i, 1] = (a * sinf(arg[i])) return result @@ -192,17 +208,39 @@ cdef float calc_costa_beta(float bw, float damp=1 / sqrt(2)) nogil: cdef float beta = (4 * bw * bw) / (1 + 2 * damp * bw + bw * bw) return beta -cdef void costa_demod(float complex[::1] samples, float[::1] result, float noise_sqrd, +cdef void costa_demod(IQ samples, float[::1] result, float noise_sqrd, float costa_alpha, float costa_beta, bool qam, long long num_samples): cdef float phase_error = 0 cdef long long i = 0 cdef float costa_freq = 0, costa_phase = 0 - cdef float complex nco_out = 0, nco_times_sample = 0, c = 0 + cdef float complex nco_out = 0, nco_times_sample = 0 cdef float real = 0, imag = 0, magnitude = 0 + cdef float scale, shift + + if str(cython.typeof(samples)) == "char[:, ::1]": + scale = 127.5 + shift = 0.5 + elif str(cython.typeof(samples)) == "unsigned char[:, ::1]": + scale = 127.5 + shift = -127.5 + elif str(cython.typeof(samples)) == "short[:, ::1]": + scale = 32767.5 + shift = 0.5 + elif str(cython.typeof(samples)) == "unsigned short[:, ::1]": + scale = 65535.0 + shift = -32767.5 + elif str(cython.typeof(samples)) == "float[:, ::1]": + scale = 1.0 + shift = 0.0 + else: + raise ValueError("Unsupported dtype") + + + cdef real_float, imag_float for i in range(0, num_samples): - c = samples[i] - real, imag = c.real, c.imag + real = samples[i, 0] + imag = samples[i, 1] magnitude = real * real + imag * imag if magnitude <= noise_sqrd: # |c| <= mag_treshold result[i] = NOISE_FSK_PSK @@ -210,9 +248,11 @@ cdef void costa_demod(float complex[::1] samples, float[::1] result, float noise # # NCO Output #nco_out = np.exp(-costa_phase * 1j) - nco_out = cos(-costa_phase) + imag_unit * sin(-costa_phase) + nco_out = cosf(-costa_phase) + imag_unit * sinf(-costa_phase) - nco_times_sample = nco_out * c + real_float = (real + shift) / scale + imag_float = (imag + shift) / scale + nco_times_sample = nco_out * (real_float + imag_unit * imag_float) phase_error = nco_times_sample.imag * nco_times_sample.real costa_freq += costa_beta * phase_error costa_phase += costa_freq + costa_alpha * phase_error @@ -221,12 +261,11 @@ cdef void costa_demod(float complex[::1] samples, float[::1] result, float noise else: result[i] = nco_times_sample.real -cpdef np.ndarray[np.float32_t, ndim=1] afp_demod(float complex[::1] samples, float noise_mag, int mod_type): +cpdef np.ndarray[np.float32_t, ndim=1] afp_demod(IQ samples, float noise_mag, int mod_type): if len(samples) <= 2: return np.zeros(len(samples), dtype=np.float32) cdef long long i = 0, ns = len(samples) - cdef float complex tmp = 0, c = 0 cdef float arg = 0 cdef float noise_sqrd = 0 cdef float complex_phase = 0 @@ -239,12 +278,27 @@ cpdef np.ndarray[np.float32_t, ndim=1] afp_demod(float complex[::1] samples, flo cdef float costa_freq = 0 cdef float costa_phase = 0 cdef complex nco_out = 0 + cdef float complex tmp cdef float phase_error = 0 cdef float costa_alpha = 0 cdef float costa_beta = 0 cdef complex nco_times_sample = 0 cdef float magnitude = 0 + cdef float max_magnitude # ensure all magnitudes of ASK demod between 0 and 1 + if str(cython.typeof(samples)) == "char[:, ::1]": + max_magnitude = sqrt(127*127 + 128*128) + elif str(cython.typeof(samples)) == "unsigned char[:, ::1]": + max_magnitude = sqrt(255*255) + elif str(cython.typeof(samples)) == "short[:, ::1]": + max_magnitude = sqrt(32768*32768 + 32767*32767) + elif str(cython.typeof(samples)) == "unsigned short[:, ::1]": + max_magnitude = sqrt(65535*65535) + elif str(cython.typeof(samples)) == "float[:, ::1]": + max_magnitude = sqrt(2) + else: + raise ValueError("Unsupported dtype") + # Atan2 liefert Werte im Bereich von -Pi bis Pi # Wir nutzen die Magic Constant NOISE_FSK_PSK um Rauschen abzuschneiden noise_sqrd = noise_mag * noise_mag @@ -262,18 +316,19 @@ cpdef np.ndarray[np.float32_t, ndim=1] afp_demod(float complex[::1] samples, flo costa_demod(samples, result, noise_sqrd, costa_alpha, costa_beta, qam, ns) else: - for i in prange(1, ns, nogil=True, schedule='static'): - c = samples[i] - real, imag = c.real, c.imag + for i in prange(1, ns, nogil=True, schedule="static"): + real = samples[i, 0] + imag = samples[i, 1] magnitude = real * real + imag * imag if magnitude <= noise_sqrd: # |c| <= mag_treshold result[i] = NOISE continue if mod_type == 0: # ASK - result[i] = sqrt(magnitude) + result[i] = sqrt(magnitude) / max_magnitude elif mod_type == 1: # FSK - tmp = samples[i - 1].conjugate() * c + #tmp = samples[i - 1].conjugate() * c + tmp = (samples[i-1, 0] - imag_unit * samples[i-1, 1]) * (real + imag_unit * imag) result[i] = atan2(tmp.imag, tmp.real) # Freq return np.asarray(result) diff --git a/src/urh/cythonext/util.pxd b/src/urh/cythonext/util.pxd new file mode 100644 index 0000000000..c9d0701682 --- /dev/null +++ b/src/urh/cythonext/util.pxd @@ -0,0 +1,8 @@ +ctypedef fused iq: + char + unsigned char + short + unsigned short + float + +ctypedef iq[:, ::1] IQ \ No newline at end of file diff --git a/src/urh/cythonext/util.pyx b/src/urh/cythonext/util.pyx index 037010fd57..5f69278009 100644 --- a/src/urh/cythonext/util.pyx +++ b/src/urh/cythonext/util.pyx @@ -8,19 +8,20 @@ import numpy as np from libc.stdint cimport uint8_t, uint16_t, uint32_t, uint64_t from libc.stdlib cimport malloc, calloc, free -cimport cython from cython.parallel import prange from libc.math cimport log10,pow from libcpp cimport bool -cpdef tuple minmax(float[:] arr): +from urh.cythonext.util cimport iq + +cpdef tuple minmax(iq[:] arr): cdef long long i, ns = len(arr) if ns == 0: - return 0,0 + return 0, 0 - cdef float maximum = arr[0] - cdef float minimum = arr[0] - cdef float e + cdef iq maximum = arr[0] + cdef iq minimum = arr[0] + cdef iq e for i in range(1, ns): e = arr[i] diff --git a/src/urh/dev/VirtualDevice.py b/src/urh/dev/VirtualDevice.py index d494aeb6a6..f36bb829e2 100644 --- a/src/urh/dev/VirtualDevice.py +++ b/src/urh/dev/VirtualDevice.py @@ -158,6 +158,13 @@ def __init__(self, backend_handler, name: str, mode: Mode, freq=None, sample_rat if mode == Mode.spectrum: self.__dev.is_in_spectrum_mode = True + @property + def data_type(self): + if self.backend == Backends.native: + return self.__dev.DATA_TYPE + else: + return np.float32 + @property def has_multi_device_support(self): return hasattr(self.__dev, "has_multi_device_support") and self.__dev.has_multi_device_support @@ -560,7 +567,7 @@ def spectrum(self): if self.backend == Backends.grc: return self.__dev.x, self.__dev.y elif self.backend == Backends.native or self.backend == Backends.network: - w = np.abs(np.fft.fft(self.__dev.receive_buffer)) + w = np.abs(np.fft.fft(self.__dev.receive_buffer.as_complex64())) freqs = np.fft.fftfreq(len(w), 1 / self.sample_rate) idx = np.argsort(freqs) return freqs[idx].astype(np.float32), w[idx].astype(np.float32) diff --git a/src/urh/dev/config.py b/src/urh/dev/config.py index c04450b185..3d69529b57 100644 --- a/src/urh/dev/config.py +++ b/src/urh/dev/config.py @@ -8,7 +8,7 @@ DEFAULT_BANDWIDTH = 1e6 DEFAULT_GAIN = 20 DEFAULT_IF_GAIN = 20 -DEFAULT_BB_GAIN = 20 +DEFAULT_BB_GAIN = 16 DEFAULT_FREQ_CORRECTION = 1 DEFAULT_DIRECT_SAMPLING_MODE = 0 diff --git a/src/urh/dev/native/AirSpy.py b/src/urh/dev/native/AirSpy.py index 916d2081d5..1d6c2cb61d 100644 --- a/src/urh/dev/native/AirSpy.py +++ b/src/urh/dev/native/AirSpy.py @@ -16,6 +16,8 @@ class AirSpy(Device): }) del DEVICE_METHODS[Device.Command.SET_BANDWIDTH.name] + DATA_TYPE = np.float32 + @classmethod def setup_device(cls, ctrl_connection: Connection, device_identifier): ret = airspy.open() @@ -49,9 +51,5 @@ def __init__(self, center_freq, sample_rate, bandwidth, gain, if_gain=1, baseban self.bandwidth_is_adjustable = False @staticmethod - def unpack_complex(buffer): - unpacked = np.frombuffer(buffer, dtype=[('r', np.float32), ('i', np.float32)]) - result = np.empty(len(unpacked), dtype=np.complex64) - result.real = unpacked["r"] - result.imag = unpacked["i"] - return result + def bytes_to_iq(buffer) -> np.ndarray: + return np.frombuffer(buffer, dtype=np.float32).reshape((-1, 2), order="C") diff --git a/src/urh/dev/native/BladeRF.py b/src/urh/dev/native/BladeRF.py index 0631544924..3580c038f9 100644 --- a/src/urh/dev/native/BladeRF.py +++ b/src/urh/dev/native/BladeRF.py @@ -20,6 +20,8 @@ class BladeRF(Device): Device.Command.SET_CHANNEL_INDEX.name: "set_channel" }) + DATA_TYPE = np.int16 + @classmethod def get_device_list(cls): return bladerf.get_device_list() @@ -97,16 +99,12 @@ def device_parameters(self): ("identifier", self.device_serial)]) @staticmethod - def unpack_complex(buffer): - unpacked = np.frombuffer(buffer, dtype=np.int16) - result = np.empty(len(unpacked)//2, dtype=np.complex64) - result.real = unpacked[::2] / 2048 - result.imag = unpacked[1::2] / 2048 - return result + def bytes_to_iq(buffer) -> np.ndarray: + return np.frombuffer(buffer, dtype=np.int16).reshape((-1, 2), order="C") << 4 @staticmethod - def pack_complex(complex_samples: np.ndarray): - arr = Array("h", 2 * len(complex_samples), lock=False) + def iq_to_bytes(iq_samples: np.ndarray): + arr = Array("h", 2 * len(iq_samples), lock=False) numpy_view = np.frombuffer(arr, dtype=np.int16) - numpy_view[:] = (2048 * complex_samples.view(np.float32)).astype(np.int16) + numpy_view[:] = iq_samples.flatten(order="C") >> 4 return arr diff --git a/src/urh/dev/native/Device.py b/src/urh/dev/native/Device.py index c44f656ccf..edc1cfbf6c 100644 --- a/src/urh/dev/native/Device.py +++ b/src/urh/dev/native/Device.py @@ -9,6 +9,7 @@ import numpy as np from urh.dev.native.SendConfig import SendConfig +from urh.signalprocessing.IQArray import IQArray from urh.util.Logger import logger from urh.util.SettingsProxy import SettingsProxy @@ -23,6 +24,8 @@ class Device(object): SYNC_TX_CHUNK_SIZE = 0 CONTINUOUS_TX_CHUNK_SIZE = 0 + DATA_TYPE = np.float32 + class Command(Enum): STOP = 0 SET_FREQUENCY = 1 @@ -269,7 +272,7 @@ def __init__(self, center_freq, sample_rate, bandwidth, gain, if_gain=1, baseban self.device_serial = None self.device_number = 0 - self.samples_to_send = np.array([], dtype=np.complex64) + self.samples_to_send = np.array([], dtype=self.DATA_TYPE) self.sending_repeats = 1 # How often shall the sending sequence be repeated? 0 = forever self.resume_on_full_receive_buffer = resume_on_full_receive_buffer # for Spectrum Analyzer or Protocol Sniffing @@ -334,7 +337,7 @@ def send_config(self) -> SendConfig: total_samples = 2 * self.num_samples_to_send return SendConfig(self.send_buffer, self._current_sent_sample, self._current_sending_repeat, total_samples, self.sending_repeats, continuous=self.sending_is_continuous, - pack_complex_method=self.pack_complex, + iq_to_bytes_method=self.iq_to_bytes, continuous_send_ring_buffer=self.continuous_send_ring_buffer) @property @@ -349,7 +352,7 @@ def init_recv_buffer(self): if self.receive_buffer is None: num_samples = SettingsProxy.get_receive_buffer_size(self.resume_on_full_receive_buffer, self.is_in_spectrum_mode) - self.receive_buffer = np.zeros(int(num_samples), dtype=np.complex64, order='C') + self.receive_buffer = IQArray(None, dtype=self.DATA_TYPE, n=int(num_samples)) def log_retcode(self, retcode: int, action: str, msg=""): msg = str(msg) @@ -630,11 +633,11 @@ def stop_tx_mode(self, msg): logger.exception(e) @staticmethod - def unpack_complex(buffer) -> np.ndarray: + def bytes_to_iq(buffer) -> np.ndarray: pass @staticmethod - def pack_complex(complex_samples: np.ndarray): + def iq_to_bytes(complex_samples: np.ndarray): pass def read_device_messages(self): @@ -659,13 +662,13 @@ def read_receiving_queue(self): while self.is_receiving: try: byte_buffer = self.parent_data_conn.recv_bytes() - samples = self.unpack_complex(byte_buffer) + samples = self.bytes_to_iq(byte_buffer) n_samples = len(samples) if n_samples == 0: continue if self.apply_dc_correction: - samples -= np.mean(samples) + samples = samples - np.mean(samples, axis=0) except OSError as e: logger.exception(e) @@ -690,13 +693,21 @@ def read_receiving_queue(self): logger.debug("Exiting read_receive_queue thread.") - def init_send_parameters(self, samples_to_send: np.ndarray = None, repeats: int = None, resume=False): + def init_send_parameters(self, samples_to_send: IQArray = None, repeats: int = None, resume=False): if samples_to_send is not None: + if isinstance(samples_to_send, IQArray): + samples_to_send = samples_to_send.convert_to(self.DATA_TYPE) + else: + samples_to_send = IQArray(samples_to_send).convert_to(self.DATA_TYPE) + self.samples_to_send = samples_to_send self.send_buffer = None if self.send_buffer is None: - self.send_buffer = self.pack_complex(self.samples_to_send) + if isinstance(self.samples_to_send, IQArray): + self.send_buffer = self.iq_to_bytes(self.samples_to_send.data) + else: + self.send_buffer = self.iq_to_bytes(self.samples_to_send) elif not resume: self.current_sending_repeat = 0 diff --git a/src/urh/dev/native/HackRF.py b/src/urh/dev/native/HackRF.py index 7acbea8740..5e35910fd7 100644 --- a/src/urh/dev/native/HackRF.py +++ b/src/urh/dev/native/HackRF.py @@ -16,6 +16,8 @@ class HackRF(Device): Device.Command.SET_BANDWIDTH.name: "set_baseband_filter_bandwidth" }) + DATA_TYPE = np.int8 + @classmethod def get_device_list(cls): result = hackrf.get_device_list() @@ -90,17 +92,12 @@ def has_multi_device_support(self): return hackrf.has_multi_device_support() @staticmethod - def unpack_complex(buffer): - unpacked = np.frombuffer(buffer, dtype=[('r', np.int8), ('i', np.int8)]) - result = np.empty(len(unpacked), dtype=np.complex64) - result.real = (unpacked['r'] + 0.5) / 127.5 - result.imag = (unpacked['i'] + 0.5) / 127.5 - return result + def bytes_to_iq(buffer): + return np.frombuffer(buffer, dtype=np.int8).reshape((-1, 2), order="C") @staticmethod - def pack_complex(complex_samples: np.ndarray): - assert complex_samples.dtype == np.complex64 - arr = Array("B", 2*len(complex_samples), lock=False) - numpy_view = np.frombuffer(arr, dtype=np.int8) - numpy_view[:] = (127.5 * ((complex_samples.view(np.float32)) - 0.5 / 127.5)).astype(np.int8) + def iq_to_bytes(samples: np.ndarray): + arr = Array("B", 2 * len(samples), lock=False) + numpy_view = np.frombuffer(arr, dtype=np.uint8) + numpy_view[:] = samples.flatten(order="C") return arr diff --git a/src/urh/dev/native/LimeSDR.py b/src/urh/dev/native/LimeSDR.py index 383a30ac62..6da59f82a6 100644 --- a/src/urh/dev/native/LimeSDR.py +++ b/src/urh/dev/native/LimeSDR.py @@ -30,6 +30,8 @@ class LimeSDR(Device): Device.Command.SET_ANTENNA_INDEX.name: "set_antenna" }) + DATA_TYPE = np.float32 + @classmethod def get_device_list(cls): return limesdr.get_device_list() @@ -132,13 +134,12 @@ def device_parameters(self): ("identifier", self.device_serial)]) @staticmethod - def unpack_complex(buffer): - return np.frombuffer(buffer, dtype=np.complex64) + def bytes_to_iq(buffer): + return np.frombuffer(buffer, dtype=np.float32).reshape((-1, 2), order="C") @staticmethod - def pack_complex(complex_samples: np.ndarray): - # We can pass the complex samples directly to the LimeSDR Send API - arr = Array("f", 2 * len(complex_samples), lock=False) + def iq_to_bytes(samples: np.ndarray): + arr = Array("f", 2 * len(samples), lock=False) numpy_view = np.frombuffer(arr, dtype=np.float32) - numpy_view[:] = complex_samples.view(np.float32) + numpy_view[:] = samples.flatten(order="C") return arr diff --git a/src/urh/dev/native/PlutoSDR.py b/src/urh/dev/native/PlutoSDR.py index 834c853ca7..2755490d1d 100644 --- a/src/urh/dev/native/PlutoSDR.py +++ b/src/urh/dev/native/PlutoSDR.py @@ -16,6 +16,8 @@ class PlutoSDR(Device): DEVICE_LIB = plutosdr ASYNCHRONOUS = False + DATA_TYPE = np.int16 + @classmethod def get_device_list(cls): descs, uris = plutosdr.scan_devices() @@ -96,18 +98,12 @@ def device_parameters(self): ("identifier", self.device_serial)]) @staticmethod - def unpack_complex(buffer): - unpacked = np.frombuffer(buffer, dtype=np.int16) - result = np.empty(len(unpacked)//2, dtype=np.complex64) - result.real = unpacked[::2] / 2048 - result.imag = unpacked[1::2] / 2048 - - return result + def bytes_to_iq(buffer) -> np.ndarray: + return np.frombuffer(buffer, dtype=np.int16).reshape((-1, 2), order="C") << 4 @staticmethod - def pack_complex(complex_samples: np.ndarray): - arr = Array("h", 2 * len(complex_samples), lock=False) + def iq_to_bytes(iq_samples: np.ndarray): + arr = Array("h", 2 * len(iq_samples), lock=False) numpy_view = np.frombuffer(arr, dtype=np.int16) - # https://wiki.analog.com/resources/eval/user-guides/ad-fmcomms2-ebz/software/basic_iq_datafiles#binary_format - numpy_view[:] = np.left_shift((2048 * complex_samples.view(np.float32)).astype(np.int16), 4) + numpy_view[:] = iq_samples.flatten(order="C") >> 4 return arr diff --git a/src/urh/dev/native/RTLSDR.py b/src/urh/dev/native/RTLSDR.py index a0d52b279c..3b5fcf23e4 100644 --- a/src/urh/dev/native/RTLSDR.py +++ b/src/urh/dev/native/RTLSDR.py @@ -21,6 +21,8 @@ class RTLSDR(Device): Device.Command.SET_DIRECT_SAMPLING_MODE.name: "set_direct_sampling" }) + DATA_TYPE = np.int8 + @classmethod def get_device_list(cls): return rtlsdr.get_device_list() @@ -88,15 +90,5 @@ def set_device_bandwidth(self, bandwidth): logger.warning("Setting the bandwidth is not supported by your RTL-SDR driver version.") @staticmethod - def unpack_complex(buffer): - """ - The raw, captured IQ data is 8 bit unsigned data. - - :return: - """ - unpacked = np.frombuffer(buffer, dtype=[('r', np.uint8), ('i', np.uint8)]) - result = np.empty(len(unpacked), dtype=np.complex64) - result.real = (unpacked['r'] / 127.5) - 1.0 - result.imag = (unpacked['i'] / 127.5) - 1.0 - return result - + def bytes_to_iq(buffer): + return np.subtract(np.frombuffer(buffer, dtype=np.int8), 127).reshape((-1, 2), order="C") diff --git a/src/urh/dev/native/RTLSDRTCP.py b/src/urh/dev/native/RTLSDRTCP.py index eb543d4033..5f88a9b4f7 100644 --- a/src/urh/dev/native/RTLSDRTCP.py +++ b/src/urh/dev/native/RTLSDRTCP.py @@ -14,6 +14,8 @@ class RTLSDRTCP(Device): "testMode", "agcMode", "directSampling", "offsetTuning", "rtlXtalFreq", "tunerXtalFreq", "gainByIndex", "bandwidth", "biasTee"] + DATA_TYPE = np.uint8 + @staticmethod def receive_sync(data_connection, ctrl_connection, device_number: int, center_freq: int, sample_rate: int, bandwidth: int, gain: int, freq_correction: int, direct_sampling_mode: int, device_ip: str, @@ -186,14 +188,5 @@ def read_sync(self): return b'' @staticmethod - def unpack_complex(buffer): - """ - The raw, captured IQ data is 8 bit unsigned data. - - :return: - """ - unpacked = np.frombuffer(buffer, dtype=[('r', np.uint8), ('i', np.uint8)]) - result = np.empty(len(unpacked), dtype=np.complex64) - result.real = (unpacked['r'] / 127.5) - 1.0 - result.imag = (unpacked['i'] / 127.5) - 1.0 - return result + def bytes_to_iq(buffer): + return np.subtract(np.frombuffer(buffer, dtype=np.int8), 127).reshape((-1, 2), order="C") diff --git a/src/urh/dev/native/SDRPlay.py b/src/urh/dev/native/SDRPlay.py index 451f9cee56..20494a41df 100644 --- a/src/urh/dev/native/SDRPlay.py +++ b/src/urh/dev/native/SDRPlay.py @@ -16,6 +16,8 @@ class SDRPlay(Device): DEVICE_METHODS[Device.Command.SET_IF_GAIN.name]["rx"] = "set_if_gain" DEVICE_METHODS[Device.Command.SET_ANTENNA_INDEX.name] = "set_antenna" + DATA_TYPE = np.int16 + def __init__(self, center_freq, sample_rate, bandwidth, gain, if_gain=1, baseband_gain=1, resume_on_full_receive_buffer=False): super().__init__(center_freq=center_freq, sample_rate=sample_rate, bandwidth=bandwidth, @@ -115,11 +117,5 @@ def shutdown_device(cls, ctrl_connection, is_tx: bool): ctrl_connection.send("RELEASE DEVICE:" + str(ret)) @staticmethod - def unpack_complex(buffer): - """ - Conversion from short to float happens in c callback - :param buffer: - :param nvalues: - :return: - """ - return np.frombuffer(buffer, dtype=np.complex64) + def bytes_to_iq(buffer): + return np.frombuffer(buffer, dtype=np.int16).reshape((-1, 2), order="C") diff --git a/src/urh/dev/native/SendConfig.py b/src/urh/dev/native/SendConfig.py index a4353c7301..e1dcec8c88 100644 --- a/src/urh/dev/native/SendConfig.py +++ b/src/urh/dev/native/SendConfig.py @@ -8,14 +8,14 @@ class SendConfig(object): def __init__(self, send_buffer, current_sent_index: Value, current_sending_repeat: Value, total_samples: int, sending_repeats: int, continuous: bool = False, - pack_complex_method: callable = None, continuous_send_ring_buffer: RingBuffer = None): + iq_to_bytes_method: callable = None, continuous_send_ring_buffer: RingBuffer = None): self.send_buffer = send_buffer self.current_sent_index = current_sent_index self.current_sending_repeat = current_sending_repeat self.total_samples = total_samples self.sending_repeats = sending_repeats self.continuous = continuous - self.pack_complex_method = pack_complex_method + self.iq_to_bytes_method = iq_to_bytes_method self.continuous_send_ring_buffer = continuous_send_ring_buffer def get_data_to_send(self, buffer_length: int): @@ -24,7 +24,7 @@ def get_data_to_send(self, buffer_length: int): return np.zeros(1, dtype=self.send_buffer._type_._type_) if self.continuous: - result = self.pack_complex_method(self.continuous_send_ring_buffer.pop(buffer_length // 2)) + result = self.iq_to_bytes_method(self.continuous_send_ring_buffer.pop(buffer_length // 2)) if len(result) == 0: # avoid empty arrays which will not work with cython API return np.zeros(1, dtype=self.send_buffer._type_._type_) diff --git a/src/urh/dev/native/SoundCard.py b/src/urh/dev/native/SoundCard.py index b7f53932e9..4735337542 100644 --- a/src/urh/dev/native/SoundCard.py +++ b/src/urh/dev/native/SoundCard.py @@ -23,6 +23,8 @@ class SoundCard(Device): pyaudio_handle = None pyaudio_stream = None + DATA_TYPE = np.float32 + @classmethod def init_device(cls, ctrl_connection: Connection, is_tx: bool, parameters: OrderedDict) -> bool: try: @@ -110,12 +112,12 @@ def device_parameters(self) -> OrderedDict: ("identifier", None)]) @staticmethod - def unpack_complex(buffer): - return np.frombuffer(buffer, dtype=np.complex64) + def bytes_to_iq(buffer): + return np.frombuffer(buffer, dtype=np.float32).reshape((-1, 2), order="C") @staticmethod - def pack_complex(complex_samples: np.ndarray): - arr = Array("f", 2*len(complex_samples), lock=False) + def iq_to_bytes(samples: np.ndarray): + arr = Array("f", 2 * len(samples), lock=False) numpy_view = np.frombuffer(arr, dtype=np.float32) - numpy_view[:] = complex_samples.view(np.float32) + numpy_view[:] = samples.flatten(order="C") return arr diff --git a/src/urh/dev/native/USRP.py b/src/urh/dev/native/USRP.py index e65b307862..a93fc1c13a 100644 --- a/src/urh/dev/native/USRP.py +++ b/src/urh/dev/native/USRP.py @@ -16,6 +16,8 @@ class USRP(Device): DEVICE_LIB = usrp ASYNCHRONOUS = False + DATA_TYPE = np.float32 + @classmethod def get_device_list(cls): return usrp.find_devices("") @@ -95,13 +97,12 @@ def device_parameters(self): ("identifier", self.device_serial)]) @staticmethod - def unpack_complex(buffer): - return np.frombuffer(buffer, dtype=np.complex64) + def bytes_to_iq(buffer): + return np.frombuffer(buffer, dtype=np.float32).reshape((-1, 2), order="C") @staticmethod - def pack_complex(complex_samples: np.ndarray): - # We can pass the complex samples directly to the USRP Send API - arr = Array("f", 2 * len(complex_samples), lock=False) + def iq_to_bytes(samples: np.ndarray): + arr = Array("f", 2 * len(samples), lock=False) numpy_view = np.frombuffer(arr, dtype=np.float32) - numpy_view[:] = complex_samples.view(np.float32) + numpy_view[:] = samples.flatten(order="C") return arr diff --git a/src/urh/dev/native/lib/airspy.pyx b/src/urh/dev/native/lib/airspy.pyx index b9a21686bd..080b036e37 100644 --- a/src/urh/dev/native/lib/airspy.pyx +++ b/src/urh/dev/native/lib/airspy.pyx @@ -20,7 +20,7 @@ cdef object f cdef int _c_callback_recv(cairspy.airspy_transfer*transfer) with gil: global f try: - ( f)(transfer.samples) + ( f)(transfer.samples) except OSError as e: logger.warning("Cython-AirSpy:" + str(e)) return 0 diff --git a/src/urh/dev/native/lib/sdrplay.pyx b/src/urh/dev/native/lib/sdrplay.pyx index 88917ee357..fa37378adb 100644 --- a/src/urh/dev/native/lib/sdrplay.pyx +++ b/src/urh/dev/native/lib/sdrplay.pyx @@ -21,7 +21,7 @@ reset_rx = False reset_rx_request_received = False cdef void __rx_stream_callback(short *xi, short *xq, unsigned int firstSampleNum, int grChanged, int rfChanged, int fsChanged, unsigned int numSamples, unsigned int reset, void *cbContext): - cdef float* data = malloc(2*numSamples * sizeof(float)) + cdef short* data = malloc(2*numSamples * sizeof(short)) cdef unsigned int i = 0 cdef unsigned int j = 0 @@ -35,14 +35,13 @@ cdef void __rx_stream_callback(short *xi, short *xq, unsigned int firstSampleNum try: for i in range(0, numSamples): - data[j] = (xi[i] + 0.5) / 32767.5 - data[j+1] = (xq[i] + 0.5) / 32767.5 + data[j] = xi[i] + data[j+1] = xq[i] j += 2 - gstate = PyGILState_Ensure() conn = cbContext - conn.send_bytes(data) # python callback + conn.send_bytes(data) # python callback return finally: PyGILState_Release(gstate) @@ -89,7 +88,7 @@ cpdef get_devices(): result = [] for i in range(num_devs): - d = {"serial": devs[i].SerNo.decode("utf-8"), "device_ref": devs[i].DevNm.decode("utf-8"), + d = {"serial": devs[i].SerNo.decode("iso-8859-1"), "device_ref": devs[i].DevNm.decode("iso-8859-1"), "hw_version": devs[i].hwVer, "available": devs[i].devAvail} result.append(d) diff --git a/src/urh/plugins/InsertSine/InsertSinePlugin.py b/src/urh/plugins/InsertSine/InsertSinePlugin.py index 5ccc841652..276fc0ecff 100644 --- a/src/urh/plugins/InsertSine/InsertSinePlugin.py +++ b/src/urh/plugins/InsertSine/InsertSinePlugin.py @@ -7,6 +7,7 @@ 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 @@ -61,7 +62,7 @@ def dialog_ui(self) -> QDialog: @property def amplitude(self) -> float: - return self.__amplitude + return self.__amplitude * IQArray.min_max_for_dtype(self.original_data.dtype)[1] @amplitude.setter def amplitude(self, value: float): @@ -130,13 +131,12 @@ def create_dialog_connects(self): 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: - self.create_dialog_connects() 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.__num_samples = int(num_samples) self.dialog_ui.doubleSpinBoxNSamples.setValue(num_samples) self.original_data = original_data @@ -144,6 +144,7 @@ def get_insert_sine_dialog(self, original_data, position, sample_rate=None, num_ self.set_time() self.draw_sine_wave() + self.create_dialog_connects() return self.dialog_ui @@ -156,7 +157,7 @@ def draw_sine_wave(self): t = np.arange(0, self.num_samples) / self.sample_rate arg = ((2 * np.pi * self.frequency * t + self.phase) * 1j).astype(np.complex64) self.complex_wave = self.amplitude * np.exp(arg) # type: np.ndarray - self.draw_data = np.insert(self.original_data, self.position, self.complex_wave).imag.astype(np.float32) + self.draw_data = np.insert(self.original_data[:, 0], self.position, self.complex_wave.real) 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)) diff --git a/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py b/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py index 9c768ccba8..171cda2424 100644 --- a/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py +++ b/src/urh/plugins/NetworkSDRInterface/NetworkSDRInterfacePlugin.py @@ -7,6 +7,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal from urh.plugins.Plugin import SDRPlugin +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.Message import Message from urh.util.Errors import Errors from urh.util.Logger import logger @@ -15,6 +16,8 @@ class NetworkSDRInterfacePlugin(SDRPlugin): + DATA_TYPE = np.float32 + NETWORK_SDR_NAME = "Network SDR" # Display text for device combo box show_proto_sniff_dialog_clicked = pyqtSignal() sending_status_changed = pyqtSignal(bool) @@ -27,11 +30,12 @@ class NetworkSDRInterfacePlugin(SDRPlugin): class MyTCPHandler(socketserver.BaseRequestHandler): def handle(self): - received = self.request.recv(65536 * 8) + size = 2 * np.dtype(NetworkSDRInterfacePlugin.DATA_TYPE).itemsize + received = self.request.recv(65536 * size) self.data = received while received: - received = self.request.recv(65536 * 8) + received = self.request.recv(65536 * size) self.data += received if len(self.data) == 0: @@ -41,10 +45,11 @@ def handle(self): for data in filter(None, self.data.split(b"\n")): self.server.received_bits.append(NetworkSDRInterfacePlugin.bytearray_to_bit_str(data)) else: - while len(self.data) % 8 != 0: - self.data += self.request.recv(len(self.data) % 8) + while len(self.data) % size != 0: + self.data += self.request.recv(len(self.data) % size) - received = np.frombuffer(self.data, dtype=np.complex64) + received = np.frombuffer(self.data, dtype=NetworkSDRInterfacePlugin.DATA_TYPE) + received = received.reshape((len(received)//2, 2)) if len(received) + self.server.current_receive_index >= len(self.server.receive_buffer): self.server.current_receive_index = 0 @@ -86,14 +91,14 @@ def __init__(self, raw_mode=False, resume_on_full_receive_buffer=False, spectrum num_samples = SettingsProxy.get_receive_buffer_size(self.resume_on_full_receive_buffer, self.is_in_spectrum_mode) try: - self.receive_buffer = np.zeros(num_samples, dtype=np.complex64, order='C') + self.receive_buffer = IQArray(None, dtype=self.DATA_TYPE, n=num_samples) except MemoryError: logger.warning("Could not allocate buffer with {0:d} samples, trying less...") i = 0 while True: try: i += 2 - self.receive_buffer = np.zeros(num_samples // i, dtype=np.complex64, order='C') + self.receive_buffer = IQArray(None, dtype=self.DATA_TYPE, n=num_samples // i) logger.debug("Using buffer with {0:d} samples instead.".format(num_samples // i)) break except MemoryError: @@ -138,7 +143,7 @@ def current_receive_index(self, value): def free_data(self): if self.raw_mode: - self.receive_buffer = np.empty(0) + self.receive_buffer = IQArray(None, dtype=self.DATA_TYPE, n=0) else: self.received_bits[:] = [] @@ -186,8 +191,8 @@ def send_data(self, data, sock: socket.socket) -> str: except Exception as e: return str(e) - def send_raw_data(self, data: np.ndarray, num_repeats: int): - byte_data = data.tostring() + def send_raw_data(self, data: IQArray, num_repeats: int): + byte_data = data.to_bytes() rng = iter(int, 1) if num_repeats <= 0 else range(0, num_repeats) # <= 0 = forever sock = self.prepare_send_connection() diff --git a/src/urh/signalprocessing/ContinuousModulator.py b/src/urh/signalprocessing/ContinuousModulator.py index fb0555f5f9..43ea16be52 100644 --- a/src/urh/signalprocessing/ContinuousModulator.py +++ b/src/urh/signalprocessing/ContinuousModulator.py @@ -26,7 +26,7 @@ def __init__(self, messages, modulators, num_repeats=-1): self.modulators = modulators self.num_repeats = num_repeats # -1 or 0 = infinite - self.ring_buffer = RingBuffer(int(constants.CONTINUOUS_BUFFER_SIZE_MB*10**6)//8) + self.ring_buffer = RingBuffer(int(constants.CONTINUOUS_BUFFER_SIZE_MB*10**6)//8, dtype=Modulator.get_dtype()) self.current_message_index = Value("L", 0) diff --git a/src/urh/signalprocessing/Filter.py b/src/urh/signalprocessing/Filter.py index 1a3f27ffc4..758563b7de 100644 --- a/src/urh/signalprocessing/Filter.py +++ b/src/urh/signalprocessing/Filter.py @@ -30,13 +30,16 @@ def __init__(self, taps: list, filter_type: FilterType = FilterType.custom): def work(self, input_signal: np.ndarray) -> np.ndarray: if self.filter_type == FilterType.dc_correction: - return input_signal - np.mean(input_signal) + return input_signal - np.mean(input_signal, axis=0) else: - return self.apply_fir_filter(input_signal) + return self.apply_fir_filter(input_signal.flatten()) def apply_fir_filter(self, input_signal: np.ndarray) -> np.ndarray: if input_signal.dtype != np.complex64: - input_signal = np.array(input_signal, dtype=np.complex64) + tmp = np.empty(len(input_signal)//2, dtype=np.complex64) + tmp.real = input_signal[0::2] + tmp.imag = input_signal[1::2] + input_signal = tmp return signal_functions.fir_filter(input_signal, np.array(self.taps, dtype=np.complex64)) diff --git a/src/urh/signalprocessing/IQArray.py b/src/urh/signalprocessing/IQArray.py new file mode 100644 index 0000000000..83b6f02fc3 --- /dev/null +++ b/src/urh/signalprocessing/IQArray.py @@ -0,0 +1,242 @@ +import os +import tarfile +import tempfile +import wave + +import numpy as np + + +class IQArray(object): + def __init__(self, data: np.ndarray, dtype=None, n=None, skip_conversion=False): + if data is None: + self.__data = np.zeros((n, 2), dtype, order="C") + else: + if skip_conversion: + self.__data = data + else: + self.__data = self.convert_array_to_iq(data) + + assert self.__data.dtype not in (np.complex64, np.complex128) + + def __getitem__(self, item): + return self.__data[item] + + def __setitem__(self, key, value: np.ndarray): + if isinstance(value, int) or isinstance(value, float): + self.__data[key] = value + return + + if isinstance(value, IQArray): + value = value.data + if value.dtype == np.complex64 or value.dtype == np.complex128: + self.real[key] = value.real + self.imag[key] = value.imag + else: + if value.ndim == 2: + self.__data[key] = value + else: + self.__data[key] = value.reshape((-1, 2), order="C") + + def __len__(self): + return len(self.__data) + + def __eq__(self, other): + return np.array_equal(self.data, other.data) + + @property + def num_samples(self): + return self.__data.shape[0] + + @property + def minimum(self): + return self.min_max_for_dtype(self.__data.dtype)[0] + + @property + def maximum(self): + return self.min_max_for_dtype(self.__data.dtype)[1] + + @property + def data(self): + return self.__data + + @property + def real(self): + return self.__data[:, 0] + + @real.setter + def real(self, value): + self.__data[:, 0] = value + + @property + def imag(self): + return self.__data[:, 1] + + @imag.setter + def imag(self, value): + self.__data[:, 1] = value + + @property + def magnitudes_squared(self): + return self.real**2.0 + self.imag**2.0 + + @property + def magnitudes(self): + return np.sqrt(self.magnitudes_squared) + + @property + def magnitudes_normalized(self): + return self.magnitudes / np.sqrt(self.maximum**2.0 + self.minimum**2.0) + + @property + def dtype(self): + return self.__data.dtype + + def as_complex64(self): + return self.convert_to(np.float32).flatten(order="C").view(np.complex64) + + def to_bytes(self): + return self.__data.tostring() + + def subarray(self, start=None, stop=None, step=None): + return IQArray(self[start:stop:step]) + + def insert_subarray(self, pos, subarray: np.ndarray): + if subarray.ndim == 1: + if subarray.dtype == np.complex64: + subarray = subarray.view(np.float32).reshape((-1, 2), order="C") + elif subarray.dtype == np.complex128: + subarray = subarray.view(np.float64).reshape((-1, 2), order="C") + else: + subarray = subarray.reshape((-1, 2), order="C") + + self.__data = np.insert(self.__data, pos, subarray, axis=0) + + def apply_mask(self, mask: np.ndarray): + self.__data = self.__data[mask] + + def tofile(self, filename: str): + if filename.endswith(".complex16u") or filename.endswith(".cu8"): + self.convert_to(np.uint8).tofile(filename) + elif filename.endswith(".complex16s") or filename.endswith(".cs8"): + self.convert_to(np.int8).tofile(filename) + elif filename.endswith(".complex32u") or filename.endswith(".cu16"): + self.convert_to(np.uint16).tofile(filename) + elif filename.endswith(".complex32s") or filename.endswith(".cs16"): + self.convert_to(np.int16).tofile(filename) + else: + self.convert_to(np.float32).tofile(filename) + + def convert_to(self, target_dtype) -> np.ndarray: + if target_dtype == self.__data.dtype: + return self.__data + + if self.__data.dtype == np.uint8: + if target_dtype == np.int8: + return np.add(self.__data, -128, dtype=np.int8, casting="unsafe") + elif target_dtype == np.int16: + return np.add(self.__data, -128, dtype=np.int16, casting="unsafe") << 8 + elif target_dtype == np.uint16: + return self.__data.astype(np.uint16) << 8 + elif target_dtype == np.float32: + return np.add(np.multiply(self.__data, 1/128, dtype=np.float32), -1.0, dtype=np.float32) + + if self.__data.dtype == np.int8: + if target_dtype == np.uint8: + return np.add(self.__data, 128, dtype=np.uint8, casting="unsafe") + elif target_dtype == np.int16: + return self.__data.astype(np.int16) << 8 + elif target_dtype == np.uint16: + return np.add(self.__data, 128, dtype=np.uint16, casting="unsafe") << 8 + elif target_dtype == np.float32: + return np.multiply(self.__data, 1/128, dtype=np.float32) + + if self.__data.dtype == np.uint16: + if target_dtype == np.int8: + return (np.add(self.__data, -32768, dtype=np.int16, casting="unsafe") >> 8).astype(np.int8) + elif target_dtype == np.uint8: + return (self.__data >> 8).astype(np.uint8) + elif target_dtype == np.int16: + return np.add(self.__data, -32768, dtype=np.int16, casting="unsafe") + elif target_dtype == np.float32: + return np.add(np.multiply(self.__data, 1/32768, dtype=np.float32), -1.0, dtype=np.float32) + + if self.__data.dtype == np.int16: + if target_dtype == np.int8: + return (self.__data >> 8).astype(np.int8) + elif target_dtype == np.uint8: + return (np.add(self.__data, 32768, dtype=np.uint16, casting="unsafe") >> 8).astype(np.uint8) + elif target_dtype == np.uint16: + return np.add(self.__data, 32768, dtype=np.uint16, casting="unsafe") + elif target_dtype == np.float32: + return np.multiply(self.__data, 1/32768, dtype=np.float32) + + if self.__data.dtype == np.float32: + if target_dtype == np.int8: + return np.multiply(self.__data, 127, dtype=np.float32).astype(np.int8) + elif target_dtype == np.uint8: + return np.multiply(np.add(self.__data, 1.0, dtype=np.float32), 127, dtype=np.float32).astype(np.uint8) + elif target_dtype == np.int16: + return np.multiply(self.__data, 32767, dtype=np.float32).astype(np.int16) + elif target_dtype == np.uint16: + return np.multiply(np.add(self.__data, 1.0, dtype=np.float32), 32767, dtype=np.float32).astype(np.uint16) + + if target_dtype not in (np.uint8, np.int8, np.uint16, np.int16, np.float32): + raise ValueError("Data type {} not supported".format(target_dtype)) + + raise NotImplementedError("Conversion from {} to {} not supported", self.__data.dtype, target_dtype) + + @staticmethod + def from_file(filename: str): + if filename.endswith(".complex16u") or filename.endswith(".cu8"): + # two 8 bit unsigned integers + return IQArray(data=np.fromfile(filename, dtype=np.uint8)) + elif filename.endswith(".complex16s") or filename.endswith(".cs8"): + # two 8 bit signed integers + return IQArray(data=np.fromfile(filename, dtype=np.int8)) + elif filename.endswith(".complex32u") or filename.endswith(".cu16"): + # two 16 bit unsigned integers + return IQArray(data=np.fromfile(filename, dtype=np.uint16)) + elif filename.endswith(".complex32s") or filename.endswith(".cs16"): + # two 16 bit signed integers + return IQArray(data=np.fromfile(filename, dtype=np.int16)) + else: + return IQArray(data=np.fromfile(filename, dtype=np.float32)) + + @staticmethod + def convert_array_to_iq(arr: np.ndarray) -> np.ndarray: + if arr.ndim == 1: + if arr.dtype == np.complex64: + arr = arr.view(np.float32) + elif arr.dtype == np.complex128: + arr = arr.view(np.float64) + return arr.reshape((-1, 2), order="C") + elif arr.ndim == 2: + return arr + else: + raise ValueError("Too many dimensions") + + @staticmethod + def min_max_for_dtype(dtype) -> tuple: + if dtype in (np.float32, np.float64, np.complex64, np.complex128): + return -1, 1 + else: + return np.iinfo(dtype).min, np.iinfo(dtype).max + + @staticmethod + def concatenate(*args): + return IQArray(data=np.concatenate([arr.data if isinstance(arr, IQArray) else arr for arr in args[0]])) + + def save_compressed(self, filename): + with tarfile.open(filename, 'w:bz2') as tar_write: + tmp_name = tempfile.mkstemp()[1] + self.tofile(tmp_name) + tar_write.add(tmp_name) + os.remove(tmp_name) + + def export_to_wav(self, filename, num_channels, sample_rate): + f = wave.open(filename, "w") + f.setnchannels(num_channels) + f.setsampwidth(2) + f.setframerate(sample_rate) + f.writeframes(self.convert_to(np.int16)) + f.close() diff --git a/src/urh/signalprocessing/Modulator.py b/src/urh/signalprocessing/Modulator.py index 5b3f3d7f6a..e55b427132 100644 --- a/src/urh/signalprocessing/Modulator.py +++ b/src/urh/signalprocessing/Modulator.py @@ -8,6 +8,7 @@ from urh import constants from urh.cythonext import path_creator, signal_functions +from urh.signalprocessing.IQArray import IQArray from urh.ui.painting.ZoomableScene import ZoomableScene from urh.util.Formatter import Formatter @@ -50,6 +51,16 @@ def __eq__(self, other): self.param_for_one == other.param_for_one and \ self.param_for_zero == other.param_for_zero + @staticmethod + def get_dtype(): + dtype_str = constants.SETTINGS.value("modulation_dtype", "float32", str) + if dtype_str == "int8": + return np.int8 + elif dtype_str == "int16": + return np.int16 + else: + return np.float32 + @property def sample_rate(self): if self.__sample_rate is not None: @@ -142,7 +153,7 @@ def data_scene(self) -> QGraphicsScene: return scene - def modulate(self, data=None, pause=0, start=0): + def modulate(self, data=None, pause=0, start=0) -> IQArray: assert pause >= 0 if data is None: data = self.data @@ -155,39 +166,46 @@ def modulate(self, data=None, pause=0, start=0): data = array.array("B", data) if len(data) == 0: - return np.array([], dtype=np.complex64) + return IQArray(None, np.float32, 0) mod_type = self.MODULATION_TYPES[self.modulation_type] + dtype = self.get_dtype() + a = self.carrier_amplitude * IQArray.min_max_for_dtype(dtype)[1] + # add a variable here to prevent it from being garbage collected in multithreaded cases (Python 3.4) - result = np.zeros(0, dtype=np.complex64) + result = np.zeros(0, dtype=dtype) + + type_code = "b" if dtype == np.int8 else "h" if dtype == np.int16 else "f" + type_val = array.array(type_code, [0])[0] if mod_type == "FSK": - result = signal_functions.modulate_fsk(data, pause, start, self.carrier_amplitude, + result = signal_functions.modulate_fsk(data, pause, start, a, self.param_for_zero, self.param_for_one, self.carrier_phase_deg * (np.pi / 180), self.sample_rate, - self.samples_per_bit) + self.samples_per_bit, type_val) elif mod_type == "ASK": - a0 = self.carrier_amplitude * (self.param_for_zero / 100) - a1 = self.carrier_amplitude * (self.param_for_one / 100) + a0 = a * (self.param_for_zero / 100) + a1 = a * (self.param_for_one / 100) result = signal_functions.modulate_ask(data, pause, start, a0, a1, self.carrier_freq_hz, self.carrier_phase_deg * (np.pi / 180), self.sample_rate, - self.samples_per_bit) + self.samples_per_bit, type_val) elif mod_type == "PSK": phi0 = self.param_for_zero * (np.pi / 180) phi1 = self.param_for_one * (np.pi / 180) - result = signal_functions.modulate_psk(data, pause, start, self.carrier_amplitude, + result = signal_functions.modulate_psk(data, pause, start, a, self.carrier_freq_hz, phi0, phi1, self.sample_rate, - self.samples_per_bit) + self.samples_per_bit, type_val) elif mod_type == "GFSK": - result = signal_functions.modulate_gfsk(data, pause, start, self.carrier_amplitude, + result = signal_functions.modulate_gfsk(data, pause, start, a, self.param_for_zero, self.param_for_one, self.carrier_phase_deg * (np.pi / 180), self.sample_rate, - self.samples_per_bit, self.gauss_bt, self.gauss_filter_width) + self.samples_per_bit, self.gauss_bt, self.gauss_filter_width, + type_val) - return result + return IQArray(result) def to_xml(self, index: int) -> ET.Element: root = ET.Element("modulator") diff --git a/src/urh/signalprocessing/ProtocolAnalyzer.py b/src/urh/signalprocessing/ProtocolAnalyzer.py index 17a2a693e6..b61740938b 100644 --- a/src/urh/signalprocessing/ProtocolAnalyzer.py +++ b/src/urh/signalprocessing/ProtocolAnalyzer.py @@ -237,7 +237,7 @@ def get_protocol_from_signal(self): for bits, pause in zip(bit_data, pauses): middle_bit_pos = bit_sample_pos[i][int(len(bits) / 2)] start, end = middle_bit_pos, middle_bit_pos + bit_len - rssi = np.mean(np.abs(signal.data[start:end])) + rssi = np.mean(signal.iq_array.subarray(start, end).magnitudes_normalized) message = Message(bits, pause, message_type=self.default_message_type, bit_len=bit_len, rssi=rssi, decoder=self.decoder, bit_sample_pos=bit_sample_pos[i]) self.messages.append(message) diff --git a/src/urh/signalprocessing/ProtocolSniffer.py b/src/urh/signalprocessing/ProtocolSniffer.py index 84a4dbbffd..b43f8f0df0 100644 --- a/src/urh/signalprocessing/ProtocolSniffer.py +++ b/src/urh/signalprocessing/ProtocolSniffer.py @@ -10,6 +10,7 @@ from urh.ainterpretation import AutoInterpretation from urh.dev.BackendHandler import BackendHandler, Backends from urh.dev.VirtualDevice import VirtualDevice, Mode +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.Message import Message from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer from urh.signalprocessing.Signal import Signal @@ -43,12 +44,15 @@ def __init__(self, bit_len: int, center: float, noise: float, tolerance: int, self.rcv_device = VirtualDevice(self.backend_handler, device, Mode.receive, resume_on_full_receive_buffer=True, raw_mode=network_raw_mode) + signal.iq_array = IQArray(None, self.rcv_device.data_type, 0) + self.sniff_thread = Thread(target=self.check_for_data, daemon=True) self.rcv_device.started.connect(self.__emit_started) self.rcv_device.stopped.connect(self.__emit_stopped) - self.__buffer = np.zeros(int(self.BUFFER_SIZE_MB * 1000 * 1000 / 8), dtype=np.complex64) + self.__buffer = IQArray(None, np.float32, 0) + self.__init_buffer() self.__current_buffer_index = 0 self.reading_data = False @@ -78,6 +82,10 @@ def __clear_buffer(self): def __buffer_is_full(self): return self.__current_buffer_index >= len(self.__buffer) - 2 + def __init_buffer(self): + self.__buffer = IQArray(None, self.rcv_device.data_type, int(self.BUFFER_SIZE_MB * 1000 * 1000 / 8)) + self.__current_buffer_index = 0 + def decoded_to_string(self, view: int, start=0, include_timestamps=True): result = [] for msg in self.messages[start:]: @@ -115,6 +123,10 @@ def device_name(self, value: str): self.rcv_device.started.connect(self.__emit_started) self.rcv_device.stopped.connect(self.__emit_stopped) + self.signal.iq_array = IQArray(None, self.rcv_device.data_type, 0) + + self.__init_buffer() + def sniff(self): self.is_running = True self.rcv_device.start() @@ -161,7 +173,7 @@ def __demodulate_data(self, data): if len(data) == 0: return - power_spectrum = data.real ** 2 + data.imag ** 2 + power_spectrum = data.real ** 2.0 + data.imag ** 2.0 is_above_noise = np.sqrt(np.mean(power_spectrum)) > self.signal.noise_threshold if self.adaptive_noise and not is_above_noise: @@ -183,7 +195,7 @@ def __demodulate_data(self, data): return # clear cache and start a new message - self.signal._fulldata = self.__buffer[0:self.__current_buffer_index] + self.signal.iq_array = IQArray(self.__buffer[0:self.__current_buffer_index]) self.__clear_buffer() self.signal._qad = None diff --git a/src/urh/signalprocessing/Signal.py b/src/urh/signalprocessing/Signal.py index 7d06a10782..915a2a2ffb 100644 --- a/src/urh/signalprocessing/Signal.py +++ b/src/urh/signalprocessing/Signal.py @@ -9,9 +9,8 @@ import urh.cythonext.signal_functions as signal_functions from urh.ainterpretation import AutoInterpretation - -from urh import constants from urh.signalprocessing.Filter import Filter +from urh.signalprocessing.IQArray import IQArray from urh.util import FileOperator from urh.util.Logger import logger @@ -35,7 +34,7 @@ class Signal(QObject): protocol_needs_update = pyqtSignal() data_edited = pyqtSignal() # On Crop/Mute/Delete etc. - def __init__(self, filename: str, name: str, modulation: str = None, sample_rate: float = 1e6, parent=None): + def __init__(self, filename: str, name="Signal", modulation: str = None, sample_rate: float = 1e6, parent=None): super().__init__(parent) self.__name = name self.__tolerance = 5 @@ -68,26 +67,12 @@ def __init__(self, filename: str, name: str, modulation: str = None, sample_rate self.__load_complex_file(filename) self.filename = filename - self.noise_threshold = AutoInterpretation.detect_noise_level(np.abs(self.data)) + self.noise_threshold = AutoInterpretation.detect_noise_level(self.iq_array.magnitudes) else: self.filename = "" def __load_complex_file(self, filename: str): - if filename.endswith(".complex16u") or filename.endswith(".cu8"): - # two 8 bit unsigned integers - raw = np.fromfile(filename, dtype=[('r', np.uint8), ('i', np.uint8)]) - self._fulldata = np.empty(raw.shape[0], dtype=np.complex64) - self._fulldata.real = (raw['r'] / 127.5) - 1.0 - self._fulldata.imag = (raw['i'] / 127.5) - 1.0 - elif filename.endswith(".complex16s") or filename.endswith(".cs8"): - # two 8 bit signed integers - raw = np.fromfile(filename, dtype=[('r', np.int8), ('i', np.int8)]) - self._fulldata = np.empty(raw.shape[0], dtype=np.complex64) - self._fulldata.real = (raw['r'] + 0.5) / 127.5 - self._fulldata.imag = (raw['i'] + 0.5) / 127.5 - else: - # Uncompressed - self._fulldata = np.fromfile(filename, dtype=np.complex64) + self.iq_array = IQArray.from_file(filename) def __load_wav_file(self, filename: str): wav = wave.open(filename, "r") @@ -117,13 +102,12 @@ def __load_wav_file(self, filename: str): else: data = np.frombuffer(byte_frames, dtype=params["fmt"]) + self.iq_array = IQArray(None, np.float32, n=num_frames) if num_channels == 1: - self._fulldata = np.zeros(num_frames, dtype=np.complex64, order="C") - self._fulldata.real = np.multiply(1 / params["max"], np.subtract(data, params["center"])) + self.iq_array.real = np.multiply(1 / params["max"], np.subtract(data, params["center"])) elif num_channels == 2: - self._fulldata = np.zeros(num_frames, dtype=np.complex64, order="C") - self._fulldata.real = np.multiply(1 / params["max"], np.subtract(data[0::2], params["center"])) - self._fulldata.imag = np.multiply(1 / params["max"], np.subtract(data[1::2], params["center"])) + self.iq_array.real = np.multiply(1 / params["max"], np.subtract(data[0::2], params["center"])) + self.iq_array.imag = np.multiply(1 / params["max"], np.subtract(data[1::2], params["center"])) else: raise ValueError("Can't handle {0} channels. Only 1 and 2 are supported.".format(num_channels)) @@ -136,7 +120,7 @@ def __load_compressed_complex(self, filename: str): members = obj.getmembers() obj.extract(members[0], QDir.tempPath()) extracted_filename = os.path.join(QDir.tempPath(), obj.getnames()[0]) - self._fulldata = np.fromfile(extracted_filename, dtype=np.complex64) + self.__load_complex_file(extracted_filename) os.remove(extracted_filename) @property @@ -261,7 +245,7 @@ def name(self, value): @property def num_samples(self): - return len(self.data) + return self.iq_array.num_samples @property def noise_threshold(self): @@ -279,6 +263,14 @@ def noise_threshold(self, value): if not self.block_protocol_update: self.protocol_needs_update.emit() + @property + def noise_threshold_relative(self): + return self.noise_threshold / (self.iq_array.maximum**2.0 + self.iq_array.minimum**2.0)**0.5 + + @noise_threshold_relative.setter + def noise_threshold_relative(self, value: float): + self.noise_threshold = value * (self.iq_array.maximum**2.0 + self.iq_array.minimum**2.0)**0.5 + @property def qad(self): if self._qad is None: @@ -286,21 +278,13 @@ def qad(self): return self._qad - @property - def data(self) -> np.ndarray: - return self._fulldata - @property def real_plot_data(self): try: - return self.data.real + return self.iq_array.real except AttributeError: return np.zeros(0, dtype=np.float32) - @property - def wave_data(self): - return (self.data.view(np.float32) * 32767).astype(np.int16) - @property def changed(self) -> bool: """ @@ -339,9 +323,9 @@ def get_signal_end(self): return signal_functions.find_signal_end(self.qad, self.modulation_type) def quad_demod(self): - return signal_functions.afp_demod(self.data, self.noise_threshold, self.modulation_type) + return signal_functions.afp_demod(self.iq_array.data, self.noise_threshold, self.modulation_type) - def calc_noise_threshold(self, noise_start: int, noise_end: int): + def calc_relative_noise_threshold_from_range(self, noise_start: int, noise_end: int): num_digits = 4 noise_start, noise_end = int(noise_start), int(noise_end) @@ -349,20 +333,19 @@ def calc_noise_threshold(self, noise_start: int, noise_end: int): noise_start, noise_end = noise_end, noise_start try: - magnitudes = np.absolute(self.data[noise_start:noise_end]) - maximum = np.max(magnitudes) + maximum = np.max(self.iq_array.subarray(noise_start, noise_end).magnitudes_normalized) return np.ceil(maximum * 10 ** num_digits) / 10 ** num_digits except ValueError: logger.warning("Could not calculate noise threshold for range {}-{}".format(noise_start, noise_end)) - return self.noise_threshold + return self.noise_threshold_relative def create_new(self, start=0, end=0, new_data=None): new_signal = Signal("", "New " + self.name) if new_data is None: - new_signal._fulldata = self.data[start:end] + new_signal.iq_array = IQArray(self.iq_array[start:end]) else: - new_signal._fulldata = new_data + new_signal.iq_array = IQArray(new_data) new_signal._noise_threshold = self.noise_threshold new_signal.noise_min_plot = self.noise_min_plot @@ -378,7 +361,7 @@ def auto_detect(self, emit_update=True, detect_modulation=True, detect_noise=Fal else "OOK" if self.__modulation_order == 2 and self.__modulation_type == 0 else self.modulation_type_str} - estimated_params = AutoInterpretation.estimate(self.data, **kwargs) + estimated_params = AutoInterpretation.estimate(self.iq_array, **kwargs) if estimated_params is None: return False @@ -418,7 +401,7 @@ def estimate_frequency(self, start: int, end: int, sample_rate: float): """ # ensure power of 2 for faster fft length = 2 ** int(math.log2(end - start)) - data = self.data[start:start + length] + data = self.iq_array.as_complex64()[start:start + length] try: w = np.fft.fft(data) @@ -433,7 +416,7 @@ def estimate_frequency(self, start: int, end: int, sample_rate: float): return freq_in_hertz def eliminate(self): - self._fulldata = None + self.iq_array = None self._qad = None self.parameter_cache.clear() @@ -441,7 +424,7 @@ def silent_set_modulation_type(self, mod_type: int): self.__modulation_type = mod_type def insert_data(self, index: int, data: np.ndarray): - self._fulldata = np.insert(self._fulldata, index, data) + self.iq_array.insert_subarray(index, data) self._qad = None self.__invalidate_after_edit() @@ -451,7 +434,7 @@ def delete_range(self, start: int, end: int): mask[start:end] = False try: - self._fulldata = self._fulldata[mask] + self.iq_array.apply_mask(mask) self._qad = self._qad[mask] if self._qad is not None else None except IndexError as e: logger.warning("Could not delete data: " + str(e)) @@ -459,21 +442,21 @@ def delete_range(self, start: int, end: int): self.__invalidate_after_edit() def mute_range(self, start: int, end: int): - self._fulldata[start:end] = 0 + self.iq_array[start:end] = 0 if self._qad is not None: self._qad[start:end] = 0 self.__invalidate_after_edit() def crop_to_range(self, start: int, end: int): - self._fulldata = self._fulldata[start:end] + self.iq_array = IQArray(self.iq_array[start:end]) self._qad = self._qad[start:end] if self._qad is not None else None self.__invalidate_after_edit() def filter_range(self, start: int, end: int, fir_filter: Filter): - self._fulldata[start:end] = fir_filter.work(self._fulldata[start:end]) - self._qad[start:end] = signal_functions.afp_demod(self.data[start:end], + self.iq_array[start:end] = fir_filter.work(self.iq_array[start:end]) + self._qad[start:end] = signal_functions.afp_demod(self.iq_array[start:end], self.noise_threshold, self.modulation_type) self.__invalidate_after_edit() @@ -486,6 +469,6 @@ def __invalidate_after_edit(self): @staticmethod def from_samples(samples: np.ndarray, name: str, sample_rate: float): signal = Signal("", name, sample_rate=sample_rate) - signal._fulldata = samples + signal.iq_array = IQArray(samples) return signal diff --git a/src/urh/signalprocessing/Spectrogram.py b/src/urh/signalprocessing/Spectrogram.py index 2fed0ab77c..a1f2893219 100644 --- a/src/urh/signalprocessing/Spectrogram.py +++ b/src/urh/signalprocessing/Spectrogram.py @@ -5,6 +5,7 @@ from urh import colormaps from urh.cythonext import util +from urh.signalprocessing.IQArray import IQArray from urh.util.Logger import logger @@ -21,6 +22,9 @@ def __init__(self, samples: np.ndarray, window_size=DEFAULT_FFT_WINDOW_SIZE, :param overlap_factor: Value between 0 (= No Overlapping) and 1 (= Full overlapping) of windows :param window_function: Function for DFT window """ + if isinstance(samples, IQArray): + samples = samples.as_complex64() + self.__samples = samples self.__window_size = window_size self.__overlap_factor = overlap_factor diff --git a/src/urh/ui/actions/EditSignalAction.py b/src/urh/ui/actions/EditSignalAction.py index fac5337b23..d161fc8ddf 100644 --- a/src/urh/ui/actions/EditSignalAction.py +++ b/src/urh/ui/actions/EditSignalAction.py @@ -4,6 +4,7 @@ from PyQt5.QtWidgets import QUndoCommand from urh.signalprocessing.Filter import Filter +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer from urh.signalprocessing.Signal import Signal @@ -54,8 +55,8 @@ def __init__(self, signal: Signal, mode: EditAction, if self.mode == EditAction.crop: self.setText("Crop Signal") - self.pre_crop_data = self.signal._fulldata[0:self.start] - self.post_crop_data = self.signal._fulldata[self.end:] + self.pre_crop_data = self.signal.iq_array[0:self.start] + self.post_crop_data = self.signal.iq_array[self.end:] if self.cache_qad: self.pre_crop_qad = self.signal._qad[0:self.start] self.post_crop_qad = self.signal._qad[self.end:] @@ -64,12 +65,12 @@ def __init__(self, signal: Signal, mode: EditAction, self.setText("Mute Signal") elif self.mode == EditAction.filter: self.setText("Filter Signal") - self.orig_data_part = copy.copy(self.signal._fulldata[self.start:self.end]) + self.orig_data_part = copy.copy(self.signal.iq_array[self.start:self.end]) if self.cache_qad and self.signal._qad is not None: self.orig_qad_part = copy.copy(self.signal._qad[self.start:self.end]) elif self.mode == EditAction.delete: self.setText("Delete Range") - self.orig_data_part = self.signal._fulldata[self.start:self.end] + self.orig_data_part = self.signal.iq_array[self.start:self.end] if self.cache_qad and self.signal._qad is not None: self.orig_qad_part = self.signal._qad[self.start:self.end] elif self.mode == EditAction.paste: @@ -137,7 +138,7 @@ def redo(self): def undo(self): if self.mode == EditAction.delete: - self.signal._fulldata = np.insert(self.signal._fulldata, self.start, self.orig_data_part) + self.signal.iq_array.insert_subarray(self.start, self.orig_data_part) if self.cache_qad and self.orig_qad_part is not None: try: self.signal._qad = np.insert(self.signal._qad, self.start, self.orig_qad_part) @@ -146,7 +147,7 @@ def undo(self): logger.warning("Could not restore cached qad.") elif self.mode == EditAction.mute or self.mode == EditAction.filter: - self.signal._fulldata[self.start:self.end] = self.orig_data_part + self.signal.iq_array[self.start:self.end] = self.orig_data_part if self.cache_qad and self.orig_qad_part is not None: try: self.signal._qad[self.start:self.end] = self.orig_qad_part @@ -155,7 +156,9 @@ def undo(self): logger.warning("Could not restore cached qad.") elif self.mode == EditAction.crop: - self.signal._fulldata = np.concatenate((self.pre_crop_data, self.signal._fulldata, self.post_crop_data)) + self.signal.iq_array = IQArray( + np.concatenate((self.pre_crop_data, self.signal.iq_array.data, self.post_crop_data)) + ) if self.cache_qad: try: self.signal._qad = np.concatenate((self.pre_crop_qad, self.signal._qad, self.post_crop_qad)) diff --git a/src/urh/ui/painting/ContinuousSceneManager.py b/src/urh/ui/painting/ContinuousSceneManager.py index 5532869941..68226f3de6 100644 --- a/src/urh/ui/painting/ContinuousSceneManager.py +++ b/src/urh/ui/painting/ContinuousSceneManager.py @@ -1,3 +1,4 @@ +from urh.signalprocessing.IQArray import IQArray from urh.ui.painting.SceneManager import SceneManager from urh.util.RingBuffer import RingBuffer @@ -9,8 +10,7 @@ def __init__(self, ring_buffer: RingBuffer, parent): self.__start = 0 self.__end = 0 - self.minimum = -1 - self.maximum = 1 + self.minimum, self.maximum = IQArray.min_max_for_dtype(self.ring_buffer.dtype) @property def plot_data(self): diff --git a/src/urh/ui/painting/LiveSceneManager.py b/src/urh/ui/painting/LiveSceneManager.py index 0edc48a03e..56ee25eb87 100644 --- a/src/urh/ui/painting/LiveSceneManager.py +++ b/src/urh/ui/painting/LiveSceneManager.py @@ -1,14 +1,14 @@ - +from urh.signalprocessing.IQArray import IQArray from urh.ui.painting.SceneManager import SceneManager + class LiveSceneManager(SceneManager): def __init__(self, data_array, parent): super().__init__(parent) self.plot_data = data_array self.end = 0 - self.minimum = -1 - self.maximum = 1 + self.minimum, self.maximum = IQArray.min_max_for_dtype(data_array.dtype) @property def num_samples(self): diff --git a/src/urh/ui/painting/SniffSceneManager.py b/src/urh/ui/painting/SniffSceneManager.py index 8be36b036e..a2a724ab7d 100644 --- a/src/urh/ui/painting/SniffSceneManager.py +++ b/src/urh/ui/painting/SniffSceneManager.py @@ -1,3 +1,4 @@ +from urh.signalprocessing.IQArray import IQArray from urh.ui.painting.SceneManager import SceneManager @@ -9,8 +10,7 @@ def __init__(self, data_array, parent, window_length=5 * 10**6): self.__end = 0 self.window_length = window_length - self.minimum = -1 - self.maximum = 1 + self.minimum, self.maximum = IQArray.min_max_for_dtype(data_array.dtype) @property def plot_data(self): diff --git a/src/urh/ui/ui_options.py b/src/urh/ui/ui_options.py index 28ee73ae88..d9a4360dad 100644 --- a/src/urh/ui/ui_options.py +++ b/src/urh/ui/ui_options.py @@ -6,10 +6,11 @@ from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_DialogOptions(object): def setupUi(self, DialogOptions): DialogOptions.setObjectName("DialogOptions") - DialogOptions.resize(803, 822) + DialogOptions.resize(814, 822) icon = QtGui.QIcon.fromTheme("configure") DialogOptions.setWindowIcon(icon) self.verticalLayout_6 = QtWidgets.QVBoxLayout(DialogOptions) @@ -18,26 +19,41 @@ def setupUi(self, DialogOptions): self.tabWidget.setObjectName("tabWidget") self.tabGeneration = QtWidgets.QWidget() self.tabGeneration.setObjectName("tabGeneration") - self.layoutWidget = QtWidgets.QWidget(self.tabGeneration) - self.layoutWidget.setGeometry(QtCore.QRect(20, 20, 324, 102)) - self.layoutWidget.setObjectName("layoutWidget") - self.gridLayout_4 = QtWidgets.QGridLayout(self.layoutWidget) - self.gridLayout_4.setContentsMargins(0, 0, 0, 0) + self.verticalLayout_9 = QtWidgets.QVBoxLayout(self.tabGeneration) + self.verticalLayout_9.setObjectName("verticalLayout_9") + self.gridLayout_4 = QtWidgets.QGridLayout() self.gridLayout_4.setObjectName("gridLayout_4") - self.labelFuzzingSamples = QtWidgets.QLabel(self.layoutWidget) + self.labelFuzzingSamples = QtWidgets.QLabel(self.tabGeneration) self.labelFuzzingSamples.setObjectName("labelFuzzingSamples") self.gridLayout_4.addWidget(self.labelFuzzingSamples, 1, 1, 1, 1) - self.checkBoxDefaultFuzzingPause = QtWidgets.QCheckBox(self.layoutWidget) + self.checkBoxDefaultFuzzingPause = QtWidgets.QCheckBox(self.tabGeneration) self.checkBoxDefaultFuzzingPause.setObjectName("checkBoxDefaultFuzzingPause") self.gridLayout_4.addWidget(self.checkBoxDefaultFuzzingPause, 0, 0, 1, 2) - self.doubleSpinBoxFuzzingPause = KillerDoubleSpinBox(self.layoutWidget) + self.doubleSpinBoxFuzzingPause = KillerDoubleSpinBox(self.tabGeneration) self.doubleSpinBoxFuzzingPause.setDecimals(3) self.doubleSpinBoxFuzzingPause.setMaximum(999999999.0) self.doubleSpinBoxFuzzingPause.setObjectName("doubleSpinBoxFuzzingPause") self.gridLayout_4.addWidget(self.doubleSpinBoxFuzzingPause, 1, 0, 1, 1) - self.checkBoxMultipleModulations = QtWidgets.QCheckBox(self.layoutWidget) + self.checkBoxMultipleModulations = QtWidgets.QCheckBox(self.tabGeneration) self.checkBoxMultipleModulations.setObjectName("checkBoxMultipleModulations") self.gridLayout_4.addWidget(self.checkBoxMultipleModulations, 2, 0, 1, 2) + self.verticalLayout_9.addLayout(self.gridLayout_4) + self.groupBoxModulationAccuracy = QtWidgets.QGroupBox(self.tabGeneration) + self.groupBoxModulationAccuracy.setObjectName("groupBoxModulationAccuracy") + self.verticalLayout_7 = QtWidgets.QVBoxLayout(self.groupBoxModulationAccuracy) + self.verticalLayout_7.setObjectName("verticalLayout_7") + self.radioButtonLowModulationAccuracy = QtWidgets.QRadioButton(self.groupBoxModulationAccuracy) + self.radioButtonLowModulationAccuracy.setObjectName("radioButtonLowModulationAccuracy") + self.verticalLayout_7.addWidget(self.radioButtonLowModulationAccuracy) + self.radioButtonMediumModulationAccuracy = QtWidgets.QRadioButton(self.groupBoxModulationAccuracy) + self.radioButtonMediumModulationAccuracy.setObjectName("radioButtonMediumModulationAccuracy") + self.verticalLayout_7.addWidget(self.radioButtonMediumModulationAccuracy) + self.radioButtonHighModulationAccuracy = QtWidgets.QRadioButton(self.groupBoxModulationAccuracy) + self.radioButtonHighModulationAccuracy.setObjectName("radioButtonHighModulationAccuracy") + self.verticalLayout_7.addWidget(self.radioButtonHighModulationAccuracy) + self.verticalLayout_9.addWidget(self.groupBoxModulationAccuracy) + spacerItem = QtWidgets.QSpacerItem(20, 500, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_9.addItem(spacerItem) self.tabWidget.addTab(self.tabGeneration, "") self.tabView = QtWidgets.QWidget() self.tabView.setObjectName("tabView") @@ -115,7 +131,7 @@ def setupUi(self, DialogOptions): self.scrollAreaSpectrogramColormap.setWidgetResizable(True) self.scrollAreaSpectrogramColormap.setObjectName("scrollAreaSpectrogramColormap") self.scrollAreaWidgetSpectrogramColormapContents = QtWidgets.QWidget() - self.scrollAreaWidgetSpectrogramColormapContents.setGeometry(QtCore.QRect(0, 0, 723, 404)) + self.scrollAreaWidgetSpectrogramColormapContents.setGeometry(QtCore.QRect(0, 0, 762, 397)) self.scrollAreaWidgetSpectrogramColormapContents.setObjectName("scrollAreaWidgetSpectrogramColormapContents") self.verticalLayout_4 = QtWidgets.QVBoxLayout(self.scrollAreaWidgetSpectrogramColormapContents) self.verticalLayout_4.setObjectName("verticalLayout_4") @@ -145,12 +161,12 @@ def setupUi(self, DialogOptions): self.btnRemoveLabeltype.setIcon(icon) self.btnRemoveLabeltype.setObjectName("btnRemoveLabeltype") self.verticalLayout_3.addWidget(self.btnRemoveLabeltype) - spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout_3.addItem(spacerItem) + spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_3.addItem(spacerItem1) self.horizontalLayout_3.addLayout(self.verticalLayout_3) self.verticalLayout_5.addLayout(self.horizontalLayout_3) - spacerItem1 = QtWidgets.QSpacerItem(20, 203, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) - self.verticalLayout_5.addItem(spacerItem1) + spacerItem2 = QtWidgets.QSpacerItem(20, 203, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.verticalLayout_5.addItem(spacerItem2) self.tabWidget.addTab(self.tabFieldtypes, "") self.tab_plugins = QtWidgets.QWidget() self.tab_plugins.setObjectName("tab_plugins") @@ -284,7 +300,7 @@ def setupUi(self, DialogOptions): self.verticalLayout_6.addWidget(self.tabWidget) self.retranslateUi(DialogOptions) - self.tabWidget.setCurrentIndex(1) + self.tabWidget.setCurrentIndex(0) def retranslateUi(self, DialogOptions): _translate = QtCore.QCoreApplication.translate @@ -293,6 +309,10 @@ def retranslateUi(self, DialogOptions): self.checkBoxDefaultFuzzingPause.setToolTip(_translate("DialogOptions", "

If you disable the default pause, the pause of the fuzzed message will be used.

")) self.checkBoxDefaultFuzzingPause.setText(_translate("DialogOptions", "Use a default pause for fuzzed messages")) self.checkBoxMultipleModulations.setText(_translate("DialogOptions", "Enable modulation profiles")) + self.groupBoxModulationAccuracy.setTitle(_translate("DialogOptions", "Modulation Accuracy")) + self.radioButtonLowModulationAccuracy.setText(_translate("DialogOptions", "Low (2x8 bit) - Recommended for HackRF and RTL-SDR")) + self.radioButtonMediumModulationAccuracy.setText(_translate("DialogOptions", "Medium (2x16 bit) - Recommended for BladeRF, PlutoSDR and SDRPlay")) + self.radioButtonHighModulationAccuracy.setText(_translate("DialogOptions", "High (2x32 bit) - Recommended if you are not sure what to choose")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabGeneration), _translate("DialogOptions", "Generation")) self.label_7.setText(_translate("DialogOptions", "Default View:")) self.comboBoxDefaultView.setItemText(0, _translate("DialogOptions", "Bit")) @@ -347,4 +367,5 @@ def retranslateUi(self, DialogOptions): self.doubleSpinBoxRAMThreshold.setSuffix(_translate("DialogOptions", "%")) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabDevices), _translate("DialogOptions", "Device")) + from urh.ui.KillerDoubleSpinBox import KillerDoubleSpinBox diff --git a/src/urh/ui/ui_send_recv_device_settings.py b/src/urh/ui/ui_send_recv_device_settings.py index be7dc15b2c..a8d3d3f362 100644 --- a/src/urh/ui/ui_send_recv_device_settings.py +++ b/src/urh/ui/ui_send_recv_device_settings.py @@ -6,10 +6,11 @@ from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_FormDeviceSettings(object): def setupUi(self, FormDeviceSettings): FormDeviceSettings.setObjectName("FormDeviceSettings") - FormDeviceSettings.resize(860, 668) + FormDeviceSettings.resize(860, 711) self.verticalLayout = QtWidgets.QVBoxLayout(FormDeviceSettings) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") @@ -234,6 +235,7 @@ def setupUi(self, FormDeviceSettings): self.labelDCCorrection.setObjectName("labelDCCorrection") self.gridLayout.addWidget(self.labelDCCorrection, 15, 0, 1, 1) self.checkBoxDCCorrection = QtWidgets.QCheckBox(self.frame_2) + self.checkBoxDCCorrection.setChecked(True) self.checkBoxDCCorrection.setObjectName("checkBoxDCCorrection") self.gridLayout.addWidget(self.checkBoxDCCorrection, 15, 1, 1, 1) self.gridLayout_6.addWidget(self.frame_2, 0, 0, 1, 1) @@ -303,5 +305,6 @@ def retranslateUi(self, FormDeviceSettings): self.checkBoxDCCorrection.setToolTip(_translate("FormDeviceSettings", "Apply DC correction during recording, that is, ensure the captured signal has a mean value of zero.")) self.checkBoxDCCorrection.setText(_translate("FormDeviceSettings", "Apply DC correction")) + from urh.ui.KillerDoubleSpinBox import KillerDoubleSpinBox from . import urh_rc diff --git a/src/urh/ui/ui_send_recv_sniff_settings.py b/src/urh/ui/ui_send_recv_sniff_settings.py index faff9b2f04..db9f56c89d 100644 --- a/src/urh/ui/ui_send_recv_sniff_settings.py +++ b/src/urh/ui/ui_send_recv_sniff_settings.py @@ -6,10 +6,11 @@ from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_SniffSettings(object): def setupUi(self, SniffSettings): SniffSettings.setObjectName("SniffSettings") - SniffSettings.resize(482, 388) + SniffSettings.resize(482, 424) self.verticalLayout = QtWidgets.QVBoxLayout(SniffSettings) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") @@ -206,4 +207,5 @@ def retranslateUi(self, SniffSettings): self.lineEdit_sniff_OutputFile.setPlaceholderText(_translate("SniffSettings", "None")) self.checkBoxAutoCenter.setText(_translate("SniffSettings", "Automatic")) + from . import urh_rc diff --git a/src/urh/ui/ui_signal_frame.py b/src/urh/ui/ui_signal_frame.py index d641a29266..410d17376a 100644 --- a/src/urh/ui/ui_signal_frame.py +++ b/src/urh/ui/ui_signal_frame.py @@ -6,10 +6,11 @@ from PyQt5 import QtCore, QtGui, QtWidgets + class Ui_SignalFrame(object): def setupUi(self, SignalFrame): SignalFrame.setObjectName("SignalFrame") - SignalFrame.resize(1057, 539) + SignalFrame.resize(1057, 566) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Maximum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -68,6 +69,7 @@ def setupUi(self, SignalFrame): self.sliderSpectrogramMin.setObjectName("sliderSpectrogramMin") self.gridLayout_2.addWidget(self.sliderSpectrogramMin, 19, 1, 1, 1) self.spinBoxNoiseTreshold = QtWidgets.QDoubleSpinBox(SignalFrame) + self.spinBoxNoiseTreshold.setSuffix("") self.spinBoxNoiseTreshold.setDecimals(4) self.spinBoxNoiseTreshold.setMaximum(1.0) self.spinBoxNoiseTreshold.setSingleStep(0.0001) @@ -570,7 +572,7 @@ def retranslateUi(self, SignalFrame): self.lCenterOffset.setToolTip(_translate("SignalFrame", "

This is the threshold used for determining if a bit is one or zero. You can set it here or grab the middle of the area in Quadrature Demod View.

")) self.lCenterOffset.setText(_translate("SignalFrame", "Center:")) self.spinBoxCenterOffset.setToolTip(_translate("SignalFrame", "

This is the threshold used for determining if a bit is one or zero. You can set it here or grab the middle of the area in Quadrature Demod View.

")) - self.cbSignalView.setToolTip(_translate("SignalFrame", "

Choose the view of your signal. Analog, Demodulated or Spectrogram.

The quadrature demodulation uses a treshold of magnitude, to supress noise. All samples with a magnitude lower than this treshold will be eliminated (set to -127) after demod.

Tune this value by selecting a noisy area and mark it as noise using context menu.

Current noise treshold is:

")) + self.cbSignalView.setToolTip(_translate("SignalFrame", "

Choose the view of your signal. Analog, Demodulated or Spectrogram.

The quadrature demodulation uses a threshold of magnitudes, to supress noise. All samples with a magnitude lower than this treshold will be eliminated after demodulation.

Tune this value by selecting a noisy area and mark it as noise using context menu.

")) self.cbSignalView.setItemText(0, _translate("SignalFrame", "Analog")) self.cbSignalView.setItemText(1, _translate("SignalFrame", "Demodulated")) self.cbSignalView.setItemText(2, _translate("SignalFrame", "Spectrogram")) @@ -607,6 +609,7 @@ def retranslateUi(self, SignalFrame): self.lSamplesTotal.setText(_translate("SignalFrame", "0")) self.lSamplesViewText.setText(_translate("SignalFrame", "Samples in view")) + from urh.ui.views.EpicGraphicView import EpicGraphicView from urh.ui.views.LegendGraphicView import LegendGraphicView from urh.ui.views.SpectrogramGraphicView import SpectrogramGraphicView diff --git a/src/urh/ui/views/EditableGraphicView.py b/src/urh/ui/views/EditableGraphicView.py index 3311a1f1ab..1fcf4edbe3 100644 --- a/src/urh/ui/views/EditableGraphicView.py +++ b/src/urh/ui/views/EditableGraphicView.py @@ -221,7 +221,7 @@ def on_insert_sine_action_triggered(self): else: num_samples = None - original_data = self.signal.data if self.signal is not None else None + original_data = self.signal.iq_array.data if self.signal is not None else None dialog = self.insert_sine_plugin.get_insert_sine_dialog(original_data=original_data, position=self.paste_position, sample_rate=self.sample_rate, @@ -241,7 +241,7 @@ def on_insert_sine_wave_clicked(self): @pyqtSlot() def on_copy_action_triggered(self): if self.something_is_selected: - self.stored_item = self.signal._fulldata[int(self.selection_area.start):int(self.selection_area.end)] + self.stored_item = self.signal.iq_array[int(self.selection_area.start):int(self.selection_area.end)] @pyqtSlot() def on_paste_action_triggered(self): diff --git a/src/urh/util/FileOperator.py b/src/urh/util/FileOperator.py index 4700e9da4c..8ada1b1a1b 100644 --- a/src/urh/util/FileOperator.py +++ b/src/urh/util/FileOperator.py @@ -2,7 +2,6 @@ import shutil import tarfile import tempfile -import wave import zipfile import numpy as np @@ -10,7 +9,7 @@ from PyQt5.QtWidgets import QFileDialog, QMessageBox from urh.models.FileIconProvider import FileIconProvider -from urh.util.Errors import Errors +from urh.signalprocessing.IQArray import IQArray VIEW_TYPES = ["Bits", "Hex", "ASCII"] @@ -20,6 +19,12 @@ RECENT_PATH = QDir.homePath() +EXT = {np.int8: ".complex16s", np.uint8: ".complex16u", np.int16: ".complex32s", np.uint16: ".complex32u", + np.float32: ".complex", np.complex64: ".complex"} +FILTER = {np.int8: "Complex16 signed (*.complex16s *.cs8)", np.uint8: "Complex16 unsigned (*.complex16u *.cu8)", + np.uint16: "Complex32 unsigned (*.complex32u *.cu16)", np.int16: "Complex32 signed (*.complex32s *.cs16)", + np.float32: "Complex (*.complex)", np.complex64: "Complex (*.complex)"} + def get_open_dialog(directory_mode=False, parent=None, name_filter="full") -> QFileDialog: fip = FileIconProvider() @@ -37,6 +42,8 @@ def get_open_dialog(directory_mode=False, parent=None, name_filter="full") -> QF "Complex (*.complex);;" \ "Complex16 unsigned (*.complex16u *.cu8);;" \ "Complex16 signed (*.complex16s *.cs8);;" \ + "Complex32 unsigned (*.complex32u *.cu16);;" \ + "Complex32 signed (*.complex32s *.cs16);;" \ "WAV (*.wav);;" \ "Protocols (*.proto.xml *.proto);;" \ "Binary Protocols (*.bin);;" \ @@ -95,17 +102,19 @@ def uncompress_archives(file_names, temp_dir): return result -def get_save_file_name(initial_name: str, wav_only=False, caption="Save signal"): +def get_save_file_name(initial_name: str, wav_only=False, caption="Save signal", selected_name_filter=None): global RECENT_PATH if caption == "Save signal": name_filter = "Complex (*.complex);;" \ "Complex16 unsigned (*.complex16u *.cu8);;" \ "Complex16 signed (*.complex16s *.cs8);;" \ + "Complex32 unsigned (*.complex32u *.cu16);;" \ + "Complex32 signed (*.complex32s *.cs16);;" \ "Complex compressed (*.coco);;" \ "WAV (*.wav);;" \ "All Files (*)" if wav_only: - name_filter = "WAV Files (*.wav);;All Files (*)" + name_filter = "WAV (*.wav);;All Files (*)" elif caption == "Save fuzz profile": name_filter = "Fuzzing Profile (*.fuzz.xml *.fuzz);;All Files (*)" elif caption == "Save encoding": @@ -123,6 +132,10 @@ def get_save_file_name(initial_name: str, wav_only=False, caption="Save signal") dialog.setViewMode(QFileDialog.Detail) dialog.setLabelText(QFileDialog.Accept, "Save") dialog.setAcceptMode(QFileDialog.AcceptSave) + + if selected_name_filter is not None: + dialog.selectNameFilter(selected_name_filter) + dialog.selectFile(initial_name) if dialog.exec(): @@ -135,11 +148,25 @@ def get_save_file_name(initial_name: str, wav_only=False, caption="Save signal") def save_data_dialog(signal_name: str, data, sample_rate=1e6, wav_only=False, parent=None) -> str: - filename = get_save_file_name(signal_name, wav_only) + if wav_only: + if not signal_name.endswith(".wav"): + signal_name += ".wav" + name_filter = "WAV (*.wav)" + else: + if not any(signal_name.endswith(e) for e in FILTER.values()): + try: + dtype = next(d for d in EXT.keys() if d == data.dtype) + signal_name += EXT[dtype] + name_filter = FILTER[dtype] + except StopIteration: + name_filter = None + else: + name_filter = None + + filename = get_save_file_name(signal_name, wav_only, selected_name_filter=name_filter) if filename: try: - data = convert_data_to_format(data, filename) save_data(data, filename, sample_rate=sample_rate) except Exception as e: QMessageBox.critical(parent, "Error saving signal", e.args[0]) @@ -151,24 +178,15 @@ def save_data_dialog(signal_name: str, data, sample_rate=1e6, wav_only=False, pa def save_data(data, filename: str, sample_rate=1e6, num_channels=2): + if not isinstance(data, IQArray): + data = IQArray(data) + if filename.endswith(".wav"): - f = wave.open(filename, "w") - f.setnchannels(num_channels) - f.setsampwidth(2) - f.setframerate(sample_rate) - f.writeframes(data) - f.close() + data.export_to_wav(filename, num_channels, sample_rate) elif filename.endswith(".coco"): - with tarfile.open(filename, 'w:bz2') as tar_write: - tmp_name = os.path.join(QDir.tempPath(), "tmpfile") - data.tofile(tmp_name) - tar_write.add(tmp_name) - os.remove(tmp_name) + data.save_compressed(filename) else: - try: - data.tofile(filename) - except Exception as e: - Errors.write_error(e) + data.tofile(filename) if filename in archives.keys(): archive = archives[filename] @@ -178,20 +196,8 @@ def save_data(data, filename: str, sample_rate=1e6, num_channels=2): rewrite_tar(archive) -def convert_data_to_format(data: np.ndarray, filename: str): - if filename.endswith(".wav"): - return (data.view(np.float32) * 32767).astype(np.int16) - elif filename.endswith(".complex16u") or filename.endswith(".cu8"): - return (127.5 * (data.view(np.float32) + 1.0)).astype(np.uint8) - elif filename.endswith(".complex16s") or filename.endswith(".cs8"): - return (127.5 * ((data.view(np.float32)) - 0.5 / 127.5)).astype(np.int8) - else: - return data - - def save_signal(signal): - data = convert_data_to_format(signal.data, signal.filename) - save_data(data, signal.filename, sample_rate=signal.sample_rate) + save_data(signal.iq_array.data, signal.filename, signal.sample_rate) def rewrite_zip(zip_name): diff --git a/src/urh/util/RingBuffer.py b/src/urh/util/RingBuffer.py index ae4199c4f5..17634bdc9d 100644 --- a/src/urh/util/RingBuffer.py +++ b/src/urh/util/RingBuffer.py @@ -1,13 +1,19 @@ import numpy as np from multiprocessing import Value, Array +from urh.signalprocessing.IQArray import IQArray + class RingBuffer(object): """ A RingBuffer containing complex values. """ - def __init__(self, size: int): - self.__data = Array("f", 2*size) + def __init__(self, size: int, dtype=np.float32): + self.dtype = dtype + + types = {np.uint8: "B", np.int8: "b", np.int16: "h", np.uint16: "H", np.float32: "f", np.float64: "d"} + self.__data = Array(types[self.dtype], 2*size) + self.size = size self.__left_index = Value("L", 0) self.__right_index = Value("L", 0) @@ -42,7 +48,7 @@ def space_left(self): @property def data(self): - return np.frombuffer(self.__data.get_obj(), dtype=np.complex64) + return np.frombuffer(self.__data.get_obj(), dtype=self.dtype).reshape(len(self.__data) // 2, 2) @property def view_data(self): @@ -54,7 +60,7 @@ def view_data(self): if left > right: left, right = right, left - data = np.frombuffer(self.__data.get_obj(), dtype=np.complex64) + data = self.data.flatten() return np.concatenate((data[left:right], data[right:], data[:left])) def clear(self): @@ -64,7 +70,7 @@ def clear(self): def will_fit(self, number_values: int) -> bool: return number_values <= self.space_left - def push(self, values: np.ndarray): + def push(self, values: IQArray): """ Push values to buffer. If buffer can't store all values a ValueError is raised """ @@ -75,14 +81,14 @@ def push(self, values: np.ndarray): slide_1 = np.s_[self.right_index:min(self.right_index + n, self.size)] slide_2 = np.s_[:max(self.right_index + n - self.size, 0)] with self.__data.get_lock(): - data = np.frombuffer(self.__data.get_obj(), dtype=np.complex64) + data = np.frombuffer(self.__data.get_obj(), dtype=self.dtype).reshape(len(self.__data) // 2, 2) data[slide_1] = values[:slide_1.stop - slide_1.start] data[slide_2] = values[slide_1.stop - slide_1.start:] self.right_index += n self.__length.value += n - def pop(self, number: int, ensure_even_length=False): + def pop(self, number: int, ensure_even_length=False) -> np.ndarray: """ Pop number of elements. If there are not enough elements, all remaining elements are returned and the buffer is cleared afterwards. If buffer is empty, an empty numpy array is returned. @@ -93,7 +99,7 @@ def pop(self, number: int, ensure_even_length=False): number -= number % 2 if len(self) == 0 or number == 0: - return np.array([], dtype=np.complex64) + return np.array([], dtype=self.dtype) if number < 0: # take everything @@ -102,9 +108,8 @@ def pop(self, number: int, ensure_even_length=False): number = min(number, len(self)) with self.__data.get_lock(): - data = np.frombuffer(self.__data.get_obj(), dtype=np.complex64) - - result = np.empty(number, dtype=np.complex64) + result = np.ones(2*number, dtype=self.dtype).reshape(number, 2) + data = np.frombuffer(self.__data.get_obj(), dtype=self.dtype).reshape(len(self.__data) // 2, 2) if self.left_index + number > len(data): end = len(data) - self.left_index diff --git a/tests/SpectrogramTest.py b/tests/SpectrogramTest.py index c00259ff67..08526b1698 100644 --- a/tests/SpectrogramTest.py +++ b/tests/SpectrogramTest.py @@ -40,7 +40,7 @@ def setUp(self): def test_numpy_impl(self): sample_rate = 1e6 - spectrogram = np.fft.fftshift(self.stft(self.signal.data, 2**10, overlap_factor=0.5)) / 1024 + spectrogram = np.fft.fftshift(self.stft(self.signal.iq_array.data, 2**10, overlap_factor=0.5)) / 1024 ims = 10 * np.log10(spectrogram.real ** 2 + spectrogram.imag ** 2) # convert amplitudes to decibel num_time_bins, num_freq_bins = np.shape(ims) @@ -53,7 +53,7 @@ def test_numpy_impl(self): plt.ylim(ymin=0, ymax=num_freq_bins) x_tick_pos = np.linspace(0, num_time_bins - 1, 5, dtype=np.float32) - plt.xticks(x_tick_pos, ["%.02f" % l for l in (x_tick_pos * len(self.signal.data) / num_time_bins) / sample_rate]) + plt.xticks(x_tick_pos, ["%.02f" % l for l in (x_tick_pos * len(self.signal.iq_array.data) / num_time_bins) / sample_rate]) y_tick_pos = np.linspace(0, num_freq_bins - 1, 10, dtype=np.int16) frequencies = np.fft.fftshift(np.fft.fftfreq(num_freq_bins, 1/sample_rate)) plt.yticks(y_tick_pos, ["%.02f" % frequencies[i] for i in y_tick_pos]) diff --git a/tests/auto_interpretation/auto_interpretation_test_util.py b/tests/auto_interpretation/auto_interpretation_test_util.py index 26ddb3b63c..6b9bcefb8d 100644 --- a/tests/auto_interpretation/auto_interpretation_test_util.py +++ b/tests/auto_interpretation/auto_interpretation_test_util.py @@ -2,6 +2,7 @@ import numpy as np +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.Message import Message from urh.signalprocessing.Modulator import Modulator from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer @@ -10,7 +11,13 @@ def demodulate(signal_data, mod_type: str, bit_length, center, noise, tolerance, decoding=None, pause_threshold=8): signal = Signal("", "") - signal._fulldata = signal_data + if isinstance(signal_data, IQArray): + signal.iq_array = signal_data + else: + if signal_data.dtype == np.complex64: + signal.iq_array = IQArray(signal_data.view(np.float32)) + else: + signal.iq_array = IQArray(signal_data) signal.modulation_type = signal.MODULATION_TYPES.index(mod_type) signal.bit_len = bit_length signal.qad_center = center diff --git a/tests/auto_interpretation/test_additional_signals.py b/tests/auto_interpretation/test_additional_signals.py index 33c5cffa8a..49224f19d5 100644 --- a/tests/auto_interpretation/test_additional_signals.py +++ b/tests/auto_interpretation/test_additional_signals.py @@ -21,7 +21,7 @@ def test_action(self): if not path: return - data = Signal(path, "").data + data = Signal(path, "").iq_array result = AutoInterpretation.estimate(data) mod_type, bit_length = result["modulation_type"], result["bit_length"] @@ -43,7 +43,7 @@ def test_audi(self): if not path: return - data = Signal(path, "").data + data = Signal(path, "").iq_array result = AutoInterpretation.estimate(data) mod_type, bit_length = result["modulation_type"], result["bit_length"] @@ -67,7 +67,7 @@ def test_brennenstuhl(self): if not path: return - data = Signal(path, "").data + data = Signal(path, "").iq_array result = AutoInterpretation.estimate(data) mod_type, bit_length = result["modulation_type"], result["bit_length"] @@ -89,7 +89,7 @@ def test_esaver(self): if not path: return - data = Signal(path, "").data + data = Signal(path, "").iq_array result = AutoInterpretation.estimate(data) mod_type, bit_length = result["modulation_type"], result["bit_length"] @@ -111,7 +111,7 @@ def test_scislo(self): if not path: return - data = Signal(path, "").data + data = Signal(path, "").iq_array result = AutoInterpretation.estimate(data) mod_type, bit_length = result["modulation_type"], result["bit_length"] @@ -133,7 +133,7 @@ def test_vw(self): if not path: return - data = Signal(path, "").data + data = Signal(path, "").iq_array result = AutoInterpretation.estimate(data) mod_type, bit_length = result["modulation_type"], result["bit_length"] diff --git a/tests/auto_interpretation/test_auto_interpretation_integration.py b/tests/auto_interpretation/test_auto_interpretation_integration.py index 6f058cf48b..d53f401cba 100644 --- a/tests/auto_interpretation/test_auto_interpretation_integration.py +++ b/tests/auto_interpretation/test_auto_interpretation_integration.py @@ -12,7 +12,7 @@ class TestAutoInterpretationIntegration(unittest.TestCase): def test_auto_interpretation_fsk(self): - fsk_signal = np.fromfile(get_path_for_data_file("fsk.complex"), dtype=np.complex64) + fsk_signal = np.fromfile(get_path_for_data_file("fsk.complex"), dtype=np.float32) result = AutoInterpretation.estimate(fsk_signal) mod_type, bit_length = result["modulation_type"], result["bit_length"] center, noise, tolerance = result["center"], result["noise"], result["tolerance"] @@ -25,7 +25,7 @@ def test_auto_interpretation_fsk(self): "aaaaaaaac626c626f4dc1d98eef7a427999cd239d3f18") def test_auto_interpretation_ask(self): - ask_signal = np.fromfile(get_path_for_data_file("ask.complex"), dtype=np.complex64) + ask_signal = np.fromfile(get_path_for_data_file("ask.complex"), dtype=np.float32) result = AutoInterpretation.estimate(ask_signal) mod_type, bit_length = result["modulation_type"], result["bit_length"] center, noise, tolerance = result["center"], result["noise"], result["tolerance"] @@ -37,19 +37,19 @@ def test_auto_interpretation_ask(self): self.assertEqual(demodulate(ask_signal, mod_type, bit_length, center, noise, tolerance)[0], "b25b6db6c80") def test_auto_interpretation_overshoot_ook(self): - data = Signal(get_path_for_data_file("ook_overshoot.coco"), "").data + data = Signal(get_path_for_data_file("ook_overshoot.coco"), "").iq_array result = AutoInterpretation.estimate(data) self.assertEqual(result["modulation_type"], "ASK") self.assertEqual(result["bit_length"], 500) def test_auto_interpretation_enocean(self): - enocean_signal = np.fromfile(get_path_for_data_file("enocean.complex"), dtype=np.complex64) + enocean_signal = np.fromfile(get_path_for_data_file("enocean.complex"), dtype=np.float32) result = AutoInterpretation.estimate(enocean_signal) mod_type, bit_length = result["modulation_type"], result["bit_length"] center, noise, tolerance = result["center"], result["noise"], result["tolerance"] self.assertEqual(mod_type, "ASK") - self.assertGreaterEqual(center, 0.04) - self.assertLessEqual(center, 0.066) + self.assertGreaterEqual(center, 0.0077) + self.assertLessEqual(center, 0.0465) self.assertLessEqual(tolerance, 5) self.assertEqual(bit_length, 40) @@ -61,20 +61,20 @@ def test_auto_interpretation_enocean(self): def test_auto_interpretation_xavax(self): signal = Signal(get_path_for_data_file("xavax.coco"), "") - result = AutoInterpretation.estimate(signal.data) + result = AutoInterpretation.estimate(signal.iq_array.data) mod_type, bit_length = result["modulation_type"], result["bit_length"] center, noise, tolerance = result["center"], result["noise"], result["tolerance"] self.assertEqual(mod_type, "FSK") self.assertEqual(bit_length, 100) - demod = demodulate(signal.data, mod_type, bit_length, center, noise, tolerance) + demod = demodulate(signal.iq_array.data, mod_type, bit_length, center, noise, tolerance) self.assertGreaterEqual(len(demod), 5) for i in range(1, len(demod)): self.assertTrue(demod[i].startswith("aaaaaaaa")) def test_auto_interpretation_elektromaten(self): - data = Signal(get_path_for_data_file("elektromaten.coco"), "").data + data = Signal(get_path_for_data_file("elektromaten.coco"), "").iq_array result = AutoInterpretation.estimate(data) mod_type, bit_length = result["modulation_type"], result["bit_length"] @@ -91,7 +91,7 @@ def test_auto_interpretation_elektromaten(self): # Test with added 20% noise np.random.seed(5) noise = np.random.normal(loc=0, scale=1, size=2 * len(data)).astype(np.float32).view(np.complex64) - noised_data = data + 0.2 * np.mean(np.abs(data)) * noise + noised_data = data.as_complex64() + 0.2 * np.mean(data.magnitudes) * noise result = AutoInterpretation.estimate(noised_data) mod_type, bit_length = result["modulation_type"], result["bit_length"] @@ -106,7 +106,7 @@ def test_auto_interpretation_elektromaten(self): self.assertTrue(demodulated[i].startswith("8")) def test_auto_interpretation_homematic(self): - data = Signal(get_path_for_data_file("homematic.coco"), "").data + data = Signal(get_path_for_data_file("homematic.coco"), "").iq_array result = AutoInterpretation.estimate(data) mod_type, bit_length = result["modulation_type"], result["bit_length"] diff --git a/tests/auto_interpretation/test_center_detection.py b/tests/auto_interpretation/test_center_detection.py index 8af5cb4331..60bd4df504 100644 --- a/tests/auto_interpretation/test_center_detection.py +++ b/tests/auto_interpretation/test_center_detection.py @@ -25,7 +25,7 @@ def generate_rectangular_signal(bits: str, bit_len: int): self.assertLessEqual(center, 0.6) def test_noisy_rect(self): - data = np.fromfile(get_path_for_data_file("fsk.complex"), dtype=np.complex64) + data = Signal(get_path_for_data_file("fsk.complex")).iq_array.data rect = afp_demod(data, 0.008, 1)[5:15000] center = detect_center(rect) @@ -33,7 +33,7 @@ def test_noisy_rect(self): self.assertLessEqual(center, 0.02) def test_ask_center_detection(self): - data = np.fromfile(get_path_for_data_file("ask.complex"), dtype=np.complex64) + data = Signal(get_path_for_data_file("ask.complex")).iq_array.data rect = afp_demod(data, 0.01111, 0) center = detect_center(rect) @@ -41,7 +41,7 @@ def test_ask_center_detection(self): self.assertLessEqual(center, 0.06) def test_enocean_center_detection(self): - data = np.fromfile(get_path_for_data_file("enocean.complex"), dtype=np.complex64) + data = Signal(get_path_for_data_file("enocean.complex")).iq_array.data rect = afp_demod(data, 0.05, 0) messages = [rect[2107:5432], rect[20428:23758], rect[44216:47546]] @@ -53,16 +53,16 @@ def test_enocean_center_detection(self): def test_ask_50_center_detection(self): message_indices = [(0, 8000), (18000, 26000), (36000, 44000), (54000, 62000), (72000, 80000)] - data = np.fromfile(get_path_for_data_file("ask50.complex"), dtype=np.complex64) + data = Signal(get_path_for_data_file("ask50.complex")).iq_array.data rect = afp_demod(data, 0.0509, 0) for start, end in message_indices: center = detect_center(rect[start:end]) - self.assertGreaterEqual(center, 0.5326, msg="{}/{}".format(start, end)) - self.assertLessEqual(center, 0.9482, msg="{}/{}".format(start, end)) + self.assertGreaterEqual(center, 0.4, msg="{}/{}".format(start, end)) + self.assertLessEqual(center, 0.65, msg="{}/{}".format(start, end)) def test_homematic_center_detection(self): - data = Signal(get_path_for_data_file("homematic.coco"), "").data + data = Signal(get_path_for_data_file("homematic.coco"), "").iq_array.data rect = afp_demod(data, 0.0012, 1) msg1 = rect[17719:37861] @@ -77,7 +77,7 @@ def test_homematic_center_detection(self): self.assertLessEqual(center2, -0.0367) def test_noised_homematic_center_detection(self): - data = Signal(get_path_for_data_file("noised_homematic.complex"), "").data + data = Signal(get_path_for_data_file("noised_homematic.complex"), "").iq_array.data rect = afp_demod(data, 0.0, 1) center = detect_center(rect) @@ -86,25 +86,26 @@ def test_noised_homematic_center_detection(self): self.assertLess(center, 0.0024) def test_fsk_15db_center_detection(self): - data = Signal(get_path_for_data_file("FSK15.complex"), "").data + data = Signal(get_path_for_data_file("FSK15.complex"), "").iq_array.data rect = afp_demod(data, 0, 1) center = detect_center(rect) self.assertGreaterEqual(center, -0.1979) self.assertLessEqual(center, 0.1131) def test_fsk_10db_center_detection(self): - data = Signal(get_path_for_data_file("FSK10.complex"), "").data + data = Signal(get_path_for_data_file("FSK10.complex"), "").iq_array.data rect = afp_demod(data, 0, 1) center = detect_center(rect) self.assertGreaterEqual(center, -0.1413) self.assertLessEqual(center, 0.05) def test_fsk_live_capture(self): - data = Signal(get_path_for_data_file("fsk_live.coco"), "").data + data = Signal(get_path_for_data_file("fsk_live.coco"), "").iq_array.data n = 10 moving_average_filter = Filter([1/n for _ in range(n)], filter_type=FilterType.moving_average) - filtered_data = moving_average_filter.apply_fir_filter(data) + filtered_data = moving_average_filter.apply_fir_filter(data.flatten()).view(np.float32) + filtered_data = filtered_data.reshape((len(filtered_data)//2, 2)) rect = afp_demod(filtered_data, 0.0175, 1) center = detect_center(rect) diff --git a/tests/auto_interpretation/test_message_segmentation.py b/tests/auto_interpretation/test_message_segmentation.py index 00c14c68e8..3a5c1dc4de 100644 --- a/tests/auto_interpretation/test_message_segmentation.py +++ b/tests/auto_interpretation/test_message_segmentation.py @@ -4,6 +4,7 @@ from tests.test_util import get_path_for_data_file from urh.ainterpretation.AutoInterpretation import segment_messages_from_magnitudes, merge_message_segments_for_ook +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.Modulator import Modulator from urh.signalprocessing.Signal import Signal @@ -33,7 +34,7 @@ def test_segmentation_enocean_multiple_messages(self): def test_message_segmentation_fsk_xavax(self): signal = Signal(get_path_for_data_file("xavax.coco"), "") - segments = segment_messages_from_magnitudes(np.abs(signal.data), noise_threshold=0.002) + segments = segment_messages_from_magnitudes(signal.iq_array.magnitudes, noise_threshold=0.002) # Signal starts with overdrive, so one message more self.assertTrue(len(segments) == 6 or len(segments) == 7) @@ -55,15 +56,15 @@ def test_segmentation_ask_50(self): msg2 = modulator.modulate("1010101110010101", pause=20000) msg3 = modulator.modulate("1010101010101111", pause=30000) - data = np.concatenate((msg1, msg2, msg3)) + data = IQArray.concatenate((msg1, msg2, msg3)) - segments = segment_messages_from_magnitudes(np.abs(data), noise_threshold=0) + segments = segment_messages_from_magnitudes(data.magnitudes, noise_threshold=0) self.assertEqual(len(segments), 3) self.assertEqual(segments, [(0, 999), (10999, 12599), (32599, 34199)]) def test_segmentation_elektromaten(self): signal = Signal(get_path_for_data_file("elektromaten.coco"), "") - segments = segment_messages_from_magnitudes(np.abs(signal.data), noise_threshold=0.0167) + segments = segment_messages_from_magnitudes(signal.iq_array.magnitudes, noise_threshold=0.0167) segments = merge_message_segments_for_ook(segments) self.assertEqual(len(segments), 11) diff --git a/tests/auto_interpretation/test_noise_detection.py b/tests/auto_interpretation/test_noise_detection.py index 255b744d24..69092dfdb3 100644 --- a/tests/auto_interpretation/test_noise_detection.py +++ b/tests/auto_interpretation/test_noise_detection.py @@ -39,18 +39,18 @@ def test_for_noiseless_signal(self): self.assertEqual(noise_level, 0) def test_multi_messages_different_rssi(self): - data = Signal(get_path_for_data_file("multi_messages_different_rssi.coco"), "").data + data = Signal(get_path_for_data_file("multi_messages_different_rssi.coco"), "").iq_array.data noise_level = detect_noise_level(np.abs(data)) self.assertGreater(noise_level, 0.001) self.assertLess(noise_level, 0.002) def test_for_psk_signal(self): - data = Signal(get_path_for_data_file("psk_generated.complex"), "").data + data = Signal(get_path_for_data_file("psk_generated.complex"), "").iq_array.data noise_level = detect_noise_level(np.abs(data)) self.assertGreater(noise_level, 0.0067) self.assertLessEqual(noise_level, 0.0081) def test_for_noisy_fsk_15db_signal(self): - data = Signal(get_path_for_data_file("FSK15.complex"), "").data + data = Signal(get_path_for_data_file("FSK15.complex"), "").iq_array.data noise_level = detect_noise_level(np.abs(data)) self.assertEqual(noise_level, 0) diff --git a/tests/cli/test_cli_logic.py b/tests/cli/test_cli_logic.py index ced1d47a2a..42f959b08d 100644 --- a/tests/cli/test_cli_logic.py +++ b/tests/cli/test_cli_logic.py @@ -1,11 +1,12 @@ import unittest from urh.cli import urh_cli +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.Message import Message from urh.signalprocessing.Modulator import Modulator from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer from urh.signalprocessing.Signal import Signal - +import numpy as np class TestCLILogic(unittest.TestCase): def test_cli_modulate_messages(self): @@ -28,9 +29,9 @@ def test_cli_modulate_messages(self): s = Signal("", "", modulation="ASK", sample_rate=2e6) s.bit_len = 100 s.noise_threshold = 0 - s._fulldata = modulated + s.iq_array = modulated pa = ProtocolAnalyzer(s) pa.get_protocol_from_signal() self.assertEqual(len(pa.messages), 1) - self.assertEqual(pa.messages[0].plain_bits_str, bits) \ No newline at end of file + self.assertEqual(pa.messages[0].plain_bits_str, bits) diff --git a/tests/device/HackRFTests.py b/tests/device/HackRFTests.py index c5d95387cb..6f2c3687b6 100644 --- a/tests/device/HackRFTests.py +++ b/tests/device/HackRFTests.py @@ -97,13 +97,13 @@ def test_hackrf_pack_unpack(self): self.assertEqual(len(received), len(arr)) self.assertEqual(np.int8(received[0]), -128) self.assertEqual(np.int8(received[1]), -128) - unpacked = HackRF.unpack_complex(received, len(received) // 2) + unpacked = HackRF.bytes_to_iq(received, len(received) // 2) self.assertEqual(unpacked[0], complex(-1, -1)) self.assertAlmostEqual(unpacked[1], complex(0, 0), places=1) self.assertAlmostEqual(unpacked[2], complex(0, 0), places=1) self.assertEqual(unpacked[3], complex(1, 1)) - packed = HackRF.pack_complex(unpacked) + packed = HackRF.iq_to_bytes(unpacked) self.assertEqual(received, packed) def test_c_api(self): diff --git a/tests/device/TestRTLSDR.py b/tests/device/TestRTLSDR.py index 927bb8b5cb..979a3ec5c5 100644 --- a/tests/device/TestRTLSDR.py +++ b/tests/device/TestRTLSDR.py @@ -60,7 +60,7 @@ def test_receive(self): time.sleep(1) self.assertGreater(rtlsdr_class.current_recv_index, index) - def test_pack_unpack_complex(self): + def test_bytes_to_iq(self): arr = np.array([0, 0, 127.5, 127.5, 255, 255], dtype=np.uint8) self.assertEqual(arr[0], 0) self.assertEqual(arr[1], 0) @@ -70,13 +70,13 @@ def test_pack_unpack_complex(self): self.assertEqual(len(received), len(arr)) self.assertEqual(np.int8(received[0]), 0) self.assertEqual(np.int8(received[1]), 0) - unpacked = RTLSDR.unpack_complex(received, len(received) // 2) + unpacked = RTLSDR.bytes_to_iq(received, len(received) // 2) self.assertEqual(unpacked[0], complex(-1, -1)) self.assertAlmostEqual(unpacked[1], complex(0, 0), places=1) self.assertEqual(unpacked[2], complex(1, 1)) - packed = RTLSDR.pack_complex(unpacked) + packed = RTLSDR.iq_to_bytes(unpacked) self.assertEqual(received, packed) diff --git a/tests/device/TestSDRPlay.py b/tests/device/TestSDRPlay.py index 5456addf58..3fee8b49ca 100644 --- a/tests/device/TestSDRPlay.py +++ b/tests/device/TestSDRPlay.py @@ -17,7 +17,7 @@ def recv(conn: Connection): while True: t = time.time() - result = SDRPlay.unpack_complex(conn.recv_bytes()) + result = SDRPlay.bytes_to_iq(conn.recv_bytes()) print("UNPACK", time.time()-t) class TestSDRPlay(unittest.TestCase): diff --git a/tests/device/TestUSRP.py b/tests/device/TestUSRP.py index 2b5ae7626d..675e6ff285 100644 --- a/tests/device/TestUSRP.py +++ b/tests/device/TestUSRP.py @@ -45,7 +45,7 @@ def test_cython_wrapper(self): #print(received_bytes) print(i) buffer.extend(received_bytes) - #print(USRP.unpack_complex(received_bytes, len(received_bytes) // 8)) + #print(USRP.bytes_to_iq(received_bytes, len(received_bytes) // 8)) f = open("/tmp/test.complex", "wb") f.write(buffer) diff --git a/tests/test_crc_gui_integration.py b/tests/test_crc_gui_integration.py index 675e9c2ca7..d9c683e338 100644 --- a/tests/test_crc_gui_integration.py +++ b/tests/test_crc_gui_integration.py @@ -1,5 +1,4 @@ from PyQt5.QtCore import Qt -from urh.controller.MainController import MainController from tests.QtTestCase import QtTestCase from urh import constants @@ -87,7 +86,7 @@ def test_cc1101_crc(self): model = checksum_tab.ui.tableViewDataRanges.model() model.setData(model.index(0, 0), "17") - self.assertEqual(model.data(model.index(0,0)), 17) + self.assertEqual(model.data(model.index(0, 0)), 17) proto_label_dialog.ui.btnConfirm.click() @@ -145,7 +144,6 @@ def test_checksum_in_generation_tab(self): else: self.assertFalse(font.italic(), msg=str(j)) - def __add_wsp_signal(self): self.add_signal_to_form("wsp.complex") signal_frame = self.form.signal_tab_controller.signal_frames[0] diff --git a/tests/test_file_operator.py b/tests/test_file_operator.py index ef264247b3..9034d75deb 100644 --- a/tests/test_file_operator.py +++ b/tests/test_file_operator.py @@ -10,6 +10,7 @@ from PyQt5.QtWidgets import QApplication, QFileDialog from tests.QtTestCase import QtTestCase +from urh.signalprocessing.IQArray import IQArray from urh.util import FileOperator @@ -18,7 +19,7 @@ def test_save_wav(self): temp_dir = tempfile.gettempdir() os.chdir(temp_dir) self.assertFalse(os.path.isfile("test.wav")) - FileOperator.save_data(bytearray([1, 2]), "test.wav") + FileOperator.save_data(np.array([1, 2], dtype=np.int16), "test.wav") self.assertTrue(os.path.isfile("test.wav")) os.remove("test.wav") @@ -44,7 +45,7 @@ def test_uncompress_archives(self): self.assertEqual(len(self.form.signal_tab_controller.signal_frames), 5) tar_md5 = hashlib.md5(open(os.path.join(temp_dir, "test.tar.gz"), 'rb').read()).hexdigest() - self.form.signal_tab_controller.signal_frames[0].signal._fulldata = np.ones(5, dtype=np.complex64) + self.form.signal_tab_controller.signal_frames[0].signal.iq_array = IQArray(np.ones(5, dtype=np.complex64)) self.form.signal_tab_controller.signal_frames[0].signal.changed = True self.form.signal_tab_controller.signal_frames[0].ui.btnSaveSignal.click() @@ -52,7 +53,7 @@ def test_uncompress_archives(self): self.assertNotEqual(tar_md5, tar_md5_after_save) zip_md5 = hashlib.md5(open(os.path.join(temp_dir, "test.zip"), 'rb').read()).hexdigest() - self.form.signal_tab_controller.signal_frames[4].signal._fulldata = np.ones(5, dtype=np.complex64) + self.form.signal_tab_controller.signal_frames[4].signal.iq_array = IQArray(np.ones(5, dtype=np.complex64)) self.form.signal_tab_controller.signal_frames[4].signal.changed = True self.form.signal_tab_controller.signal_frames[4].ui.btnSaveSignal.click() diff --git a/tests/test_filter.py b/tests/test_filter.py index e33991b285..9366d3187d 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -21,7 +21,7 @@ def test_fir_filter(self): fir_filter = Filter(filter_taps) - filtered_signal = fir_filter.apply_fir_filter(input_signal) + filtered_signal = fir_filter.apply_fir_filter(input_signal.flatten()) expected_filtered_signal = np.array([0.25, 0.75, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 16.5], dtype=np.complex64) self.assertTrue(np.array_equal(filtered_signal, expected_filtered_signal)) @@ -54,26 +54,28 @@ def test_filter_selection(self): self.sig_frame.ui.spinBoxSelectionEnd.setValue(selection_end) self.sig_frame.ui.spinBoxSelectionEnd.editingFinished.emit() - old_signal = self.sig_frame.signal._fulldata.copy() + old_signal = self.sig_frame.signal.iq_array.data.copy() self.assertFalse(self.sig_frame.undo_stack.canUndo()) self.sig_frame.ui.btnFilter.click() self.assertTrue(self.sig_frame.undo_stack.canUndo()) - filtered_signal = self.sig_frame.signal._fulldata + filtered_signal = self.sig_frame.signal.iq_array.data self.assertEqual(len(old_signal), len(filtered_signal)) - for i, (old_sample, filtered_sample) in enumerate(zip(old_signal, filtered_signal)): + for i in range(0, len(old_signal), 2): + old_sample = complex(old_signal[i, 0], old_signal[i, 1]) + filtered_sample = complex(filtered_signal[i, 0], filtered_signal[i, 1]) if i in range(selection_start, selection_end): self.assertNotEqual(old_sample, filtered_sample, msg=str(i)) else: self.assertEqual(old_sample, filtered_sample, msg=str(i)) self.sig_frame.undo_stack.command(0).undo() - self.assertTrue(np.array_equal(old_signal, self.sig_frame.signal.data)) + self.assertTrue(np.array_equal(old_signal, self.sig_frame.signal.iq_array.data)) self.sig_frame.undo_stack.command(0).redo() - self.assertTrue(np.array_equal(filtered_signal, self.sig_frame.signal.data)) + self.assertTrue(np.array_equal(filtered_signal, self.sig_frame.signal.iq_array.data)) def test_filter_caption(self): self.assertIn("moving average", self.sig_frame.ui.btnFilter.text()) diff --git a/tests/test_iq_array.py b/tests/test_iq_array.py new file mode 100644 index 0000000000..6ac9011ca6 --- /dev/null +++ b/tests/test_iq_array.py @@ -0,0 +1,114 @@ +import unittest + +import numpy as np + +from urh.signalprocessing.IQArray import IQArray + + +class TestIQArray(unittest.TestCase): + def test_index(self): + iq_array = IQArray(np.array([1, 2, 3, 4, 5, 6], dtype=np.uint8)) + + self.assertEqual(iq_array[0][0], 1) + self.assertEqual(iq_array[0][1], 2) + + self.assertEqual(iq_array[1][0], 3) + self.assertEqual(iq_array[1][1], 4) + + self.assertEqual(iq_array[2][0], 5) + self.assertEqual(iq_array[2][1], 6) + + self.assertEqual(iq_array[1:2][0, 0], 3) + self.assertEqual(iq_array[1:2][0, 1], 4) + + self.assertEqual(iq_array[:2][0, 0], 1) + self.assertEqual(iq_array[:2][0, 1], 2) + self.assertEqual(iq_array[:2][1, 0], 3) + self.assertEqual(iq_array[:2][1, 1], 4) + + iq_array[0] = np.array([13, 37]) + self.assertEqual(iq_array[0][0], 13) + self.assertEqual(iq_array[0][1], 37) + + iq_array[0:2] = np.array([42, 42, 47, 11]) + self.assertEqual(iq_array[0][0], 42) + self.assertEqual(iq_array[0][1], 42) + self.assertEqual(iq_array[1][0], 47) + self.assertEqual(iq_array[1][1], 11) + + def test_conversion_iq16s(self): + iq16s = IQArray(np.array([-128, 0, 0, 127], dtype=np.int8)) + self.assertTrue(np.array_equal(iq16s.convert_to(np.int8).flatten(), np.array([-128, 0, 0, 127], dtype=np.int8))) + self.assertTrue(np.array_equal(iq16s.convert_to(np.uint8).flatten(), np.array([0, 128, 128, 255], dtype=np.uint8))) + + c32s = iq16s.convert_to(np.int16).flatten() + self.assertTrue(np.array_equal(c32s, np.array([-32768, 0, 0, 32512], dtype=np.int16)), msg=c32s) + + c32u = iq16s.convert_to(np.uint16).flatten() + self.assertTrue(np.array_equal(c32u, np.array([0, 32768, 32768, 65280], dtype=np.uint16)), msg=c32u) + + c64f = iq16s.convert_to(np.float32).flatten() + self.assertTrue(np.array_equal(c64f, np.array([-1, 0, 0, 0.9921875], dtype=np.float32)), msg=c64f) + + def test_conversion_iq16u(self): + iq16u = IQArray(np.array([0, 128, 128, 255], dtype=np.uint8)) + self.assertTrue(np.array_equal(iq16u.convert_to(np.uint8).flatten(), np.array([0, 128, 128, 255], dtype=np.uint8))) + iq16s = iq16u.convert_to(np.int8).flatten() + self.assertTrue(np.array_equal(iq16s, np.array([-128, 0, 0, 127], dtype=np.int8)), msg=iq16s) + + c32s = iq16u.convert_to(np.int16).flatten() + self.assertTrue(np.array_equal(c32s, np.array([-32768, 0, 0, 32512], dtype=np.int16)), msg=c32s) + + c32u = iq16u.convert_to(np.uint16).flatten() + self.assertTrue(np.array_equal(c32u, np.array([0, 32768, 32768, 65280], dtype=np.uint16)), msg=c32u) + + c64f = iq16u.convert_to(np.float32).flatten() + self.assertTrue(np.array_equal(c64f, np.array([-1, 0, 0, 0.9921875], dtype=np.float32)), msg=c64f) + + def test_conversion_iq32s(self): + iq32s = IQArray(np.array([-32768, 0, 0, 32767], dtype=np.int16)) + self.assertTrue(np.array_equal(iq32s.convert_to(np.int16).flatten(), np.array([-32768, 0, 0, 32767], dtype=np.int16))) + + iq32u = iq32s.convert_to(np.uint16).flatten() + self.assertTrue(np.array_equal(iq32u, np.array([0, 32768, 32768, 65535], dtype=np.uint16)), msg=iq32u) + + iq16s = iq32s.convert_to(np.int8).flatten() + self.assertTrue(np.array_equal(iq16s, np.array([-128, 0, 0, 127], dtype=np.int8)), msg=iq16s) + + iq16u = iq32s.convert_to(np.uint8).flatten() + self.assertTrue(np.array_equal(iq16u, np.array([0, 128, 128, 255], dtype=np.uint8)), msg=iq16u) + + iq64f = iq32s.convert_to(np.float32).flatten() + self.assertTrue(np.array_equal(iq64f, np.array([-1, 0, 0, 0.9999695], dtype=np.float32)), msg=iq64f) + + def test_conversion_iq32u(self): + iq32u = IQArray(np.array([0, 32768, 32768, 65535], dtype=np.uint16)) + self.assertTrue(np.array_equal(iq32u.convert_to(np.uint16).flatten(), np.array([0, 32768, 32768, 65535], dtype=np.uint16))) + + iq32s = iq32u.convert_to(np.int16).flatten() + self.assertTrue(np.array_equal(iq32s, np.array([-32768, 0, 0, 32767], dtype=np.int16)), msg=iq32s) + + iq16s = iq32u.convert_to(np.int8).flatten() + self.assertTrue(np.array_equal(iq16s, np.array([-128, 0, 0, 127], dtype=np.int8)), msg=iq16s) + + iq16u = iq32u.convert_to(np.uint8).flatten() + self.assertTrue(np.array_equal(iq16u, np.array([0, 128, 128, 255], dtype=np.uint8)), msg=iq16u) + + iq64f = iq32u.convert_to(np.float32).flatten() + self.assertTrue(np.array_equal(iq64f, np.array([-1, 0, 0, 0.9999695], dtype=np.float32)), msg=iq64f) + + def test_conversion_iq64f(self): + iq64f = IQArray(np.array([-1, 0, 0, 1], dtype=np.float32)) + self.assertTrue(np.array_equal(iq64f.convert_to(np.float32).flatten(), np.array([-1, 0, 0, 1], dtype=np.float32))) + + iq16u = iq64f.convert_to(np.uint8).flatten() + self.assertTrue(np.array_equal(iq16u, np.array([0, 127, 127, 254], dtype=np.uint8)), msg=iq16u) + + iq16s = iq64f.convert_to(np.int8).flatten() + self.assertTrue(np.array_equal(iq16s, np.array([-127, 0, 0, 127], dtype=np.int8)), msg=iq16s) + + iq32s = iq64f.convert_to(np.int16).flatten() + self.assertTrue(np.array_equal(iq32s, np.array([-32767, 0, 0, 32767], dtype=np.int16)), msg=iq32s) + + iq32u = iq64f.convert_to(np.uint16).flatten() + self.assertTrue(np.array_equal(iq32u, np.array([0, 32767, 32767, 65534], dtype=np.uint16)), msg=iq32u) diff --git a/tests/test_maincontroller_gui.py b/tests/test_maincontroller_gui.py index 867933f62d..846c564e49 100644 --- a/tests/test_maincontroller_gui.py +++ b/tests/test_maincontroller_gui.py @@ -116,8 +116,8 @@ def test_load_single_channel_wav(self): sig_frame = self.form.signal_tab_controller.signal_frames[0] self.assertEqual(sig_frame.signal.sample_rate, 2e6) self.assertEqual(sig_frame.signal.num_samples, 4) - self.assertNotEqual(sig_frame.signal.data.real.sum(), 0) - self.assertEqual(sig_frame.signal.data.imag.sum(), 0) + self.assertNotEqual(sig_frame.signal.iq_array.real.sum(), 0) + self.assertEqual(sig_frame.signal.iq_array.imag.sum(), 0) def test_load_stereo_wav(self): filename = os.path.join(tempfile.gettempdir(), "test_stereo.wav") @@ -132,8 +132,8 @@ def test_load_stereo_wav(self): sig_frame = self.form.signal_tab_controller.signal_frames[0] self.assertEqual(sig_frame.signal.sample_rate, 10e6) self.assertEqual(sig_frame.signal.num_samples, 3) - self.assertNotEqual(sig_frame.signal.data.real.sum(), 0) - self.assertNotEqual(sig_frame.signal.data.imag.sum(), 0) + self.assertNotEqual(sig_frame.signal.iq_array.real.sum(), 0) + self.assertNotEqual(sig_frame.signal.iq_array.imag.sum(), 0) def test_remove_file_from_directory_tree_view(self): assert isinstance(self.form, MainController) diff --git a/tests/test_modulator_gui.py b/tests/test_modulator_gui.py index d9d36d1091..9f7c8a6fea 100644 --- a/tests/test_modulator_gui.py +++ b/tests/test_modulator_gui.py @@ -67,19 +67,27 @@ def test_edit_data(self): def test_zoom(self): self.dialog.ui.gVModulated.zoom(1.1) - self.assertEqual(int(self.dialog.ui.gVModulated.view_rect().width()), - int(self.dialog.ui.gVCarrier.view_rect().width())) + self.assertIn(int(self.dialog.ui.gVModulated.view_rect().width()), + [int(self.dialog.ui.gVCarrier.view_rect().width())-1, + int(self.dialog.ui.gVCarrier.view_rect().width()), + int(self.dialog.ui.gVCarrier.view_rect().width()+1)]) - self.assertEqual(int(self.dialog.ui.gVModulated.view_rect().width()), - int(self.dialog.ui.gVData.view_rect().width())) + self.assertIn(int(self.dialog.ui.gVModulated.view_rect().width()), + [int(self.dialog.ui.gVData.view_rect().width())-1, + int(self.dialog.ui.gVData.view_rect().width()), + int(self.dialog.ui.gVData.view_rect().width()+1)]) self.dialog.ui.gVModulated.zoom(1.01) - self.assertEqual(int(self.dialog.ui.gVModulated.view_rect().width()), - int(self.dialog.ui.gVCarrier.view_rect().width())) + self.assertIn(int(self.dialog.ui.gVModulated.view_rect().width()), + [int(self.dialog.ui.gVCarrier.view_rect().width())-1, + int(self.dialog.ui.gVCarrier.view_rect().width()), + int(self.dialog.ui.gVCarrier.view_rect().width()+1)]) - self.assertEqual(int(self.dialog.ui.gVModulated.view_rect().width()), - int(self.dialog.ui.gVData.view_rect().width())) + self.assertIn(int(self.dialog.ui.gVModulated.view_rect().width()), + [int(self.dialog.ui.gVData.view_rect().width())-1, + int(self.dialog.ui.gVData.view_rect().width()), + int(self.dialog.ui.gVData.view_rect().width()+1)]) def test_edit_modulation(self): self.dialog.ui.comboBoxModulationType.setCurrentText("Amplitude Shift Keying (ASK)") diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 9338c3a22c..9fe4edaa7d 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -85,7 +85,7 @@ def test_sdr_interface_plugin(self): def test_insert_sine_plugin(self): insert_sine_plugin = self.sframe.ui.gvSignal.insert_sine_plugin num_samples = 10000 - dialog = insert_sine_plugin.get_insert_sine_dialog(original_data=self.sframe.signal.data, + dialog = insert_sine_plugin.get_insert_sine_dialog(original_data=self.sframe.signal.iq_array.data, position=2000, sample_rate=self.sframe.signal.sample_rate, num_samples=num_samples) diff --git a/tests/test_protocol_sniffer.py b/tests/test_protocol_sniffer.py index 18624c3150..184e5b1dae 100644 --- a/tests/test_protocol_sniffer.py +++ b/tests/test_protocol_sniffer.py @@ -6,6 +6,7 @@ from tests.QtTestCase import QtTestCase from urh.dev.BackendHandler import BackendHandler from urh.plugins.NetworkSDRInterface.NetworkSDRInterfacePlugin import NetworkSDRInterfacePlugin +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.Modulator import Modulator from urh.signalprocessing.ProtocolAnalyzer import ProtocolAnalyzer from urh.signalprocessing.ProtocolSniffer import ProtocolSniffer @@ -55,7 +56,7 @@ def test_protocol_sniffer(self): # verify modulation was correct pa = ProtocolAnalyzer(None) signal = Signal("", "", sample_rate=sample_rate) - signal._fulldata = np.concatenate(packages) + signal.iq_array = IQArray.concatenate(packages) signal.modulation_type = modulation_type signal.bit_len = bit_len signal.tolerance = tolerance @@ -66,12 +67,12 @@ def test_protocol_sniffer(self): self.assertEqual(pa.plain_bits_str, data) # send data - send_data = np.concatenate(packages) + send_data = IQArray.concatenate(packages) self.network_sdr_plugin_sender.send_raw_data(send_data, 1) time.sleep(1) # Send enough pauses to end sniffing - self.network_sdr_plugin_sender.send_raw_data(np.zeros(10 * bit_len, dtype=np.complex64), 1) + self.network_sdr_plugin_sender.send_raw_data(IQArray(None, np.float32, 10 * 2 * bit_len), 1) time.sleep(1) sniffer.stop() diff --git a/tests/test_ringbuffer.py b/tests/test_ringbuffer.py index 1696b411ae..2131ef4f81 100644 --- a/tests/test_ringbuffer.py +++ b/tests/test_ringbuffer.py @@ -2,6 +2,7 @@ import numpy as np +from urh.signalprocessing.IQArray import IQArray from urh.util.RingBuffer import RingBuffer @@ -10,12 +11,12 @@ def test_push(self): ring_buffer = RingBuffer(size=10) self.assertEqual(0, ring_buffer.left_index) - add1 = np.array([1, 2, 3, 4, 5], dtype=np.complex64) + add1 = IQArray(np.array([1, 2, 3, 4, 5], dtype=np.complex64)) ring_buffer.push(add1) self.assertEqual(5, ring_buffer.right_index) self.assertTrue(np.array_equal(ring_buffer.data[0:5], add1)) - add2 = np.array([10, 20, 30, 40, 50, 60], dtype=np.complex64) + add2 = IQArray(np.array([10, 20, 30, 40, 50, 60], dtype=np.complex64)) self.assertFalse(ring_buffer.will_fit(len(add2))) ring_buffer.push(add2[:-1]) self.assertTrue(np.array_equal(ring_buffer.data[5:10], add2[:-1])) @@ -23,42 +24,43 @@ def test_push(self): def test_pop(self): ring_buffer = RingBuffer(size=5) - add1 = np.array([1, 2, 3], dtype=np.complex64) + add1 = IQArray(np.array([1, 2, 3], dtype=np.complex64)) ring_buffer.push(add1) self.assertTrue(np.array_equal(add1, ring_buffer.pop(40))) self.assertTrue(ring_buffer.is_empty) - add2 = np.array([1, 2, 3, 4], dtype=np.complex64) + add2 = IQArray(np.array([1, 2, 3, 4], dtype=np.complex64)) ring_buffer.push(add2) self.assertTrue(np.array_equal(add2, ring_buffer.pop(4))) self.assertTrue(ring_buffer.is_empty) - add3 = np.array([1, 2], dtype=np.complex64) + add3 = IQArray(np.array([1, 2], dtype=np.complex64)) ring_buffer.push(add3) popped_item = ring_buffer.pop(1) self.assertTrue(np.array_equal(add3[0:1], popped_item), msg=popped_item) self.assertFalse(ring_buffer.is_empty) - add4 = np.array([7, 8, 9, 10], dtype=np.complex64) + add4 = IQArray(np.array([7, 8, 9, 10], dtype=np.complex64)) ring_buffer.push(add4) self.assertFalse(ring_buffer.will_fit(1)) - self.assertTrue(np.array_equal(np.concatenate((np.atleast_1d(add3[1]), add4)), ring_buffer.pop(5))) + + self.assertTrue(np.array_equal(np.concatenate((add3.data[1:], add4.data)), ring_buffer.pop(5))) def test_continuous_pop(self): ring_buffer = RingBuffer(size=10) - values = np.array(list(range(10)), dtype=np.complex64) + values = IQArray(np.array(list(range(10)), dtype=np.complex64)) ring_buffer.push(values) - retrieved = np.array([], dtype=np.complex64) + retrieved = np.empty(0, dtype=np.float32) for i in range(10): retrieved = np.append(retrieved, ring_buffer.pop(1)) - self.assertTrue(np.array_equal(values, retrieved)) + self.assertEqual(values, IQArray(retrieved)) def test_big_buffer(self): ring_buffer = RingBuffer(size=5) try: - ring_buffer.push(np.array([1, 2, 3, 4, 5, 6, 7], dtype=np.complex64)) + ring_buffer.push(IQArray(np.array([1, 2, 3, 4, 5, 6, 7], dtype=np.complex64))) self.assertTrue(False) except ValueError: self.assertTrue(True) @@ -69,7 +71,7 @@ def test_will_fit(self): self.assertTrue(ring_buffer.will_fit(4)) self.assertTrue(ring_buffer.will_fit(8)) self.assertFalse(ring_buffer.will_fit(9)) - ring_buffer.push(np.array([1, 2, 3, 4], dtype=np.complex64)) + ring_buffer.push(IQArray(np.array([1, 2, 3, 4], dtype=np.complex64))) self.assertEqual(ring_buffer.space_left, 4) self.assertTrue(ring_buffer.will_fit(3)) self.assertTrue(ring_buffer.will_fit(4)) diff --git a/tests/test_send_recv_dialog_gui.py b/tests/test_send_recv_dialog_gui.py index 15ae26dbf0..ee69b984c6 100644 --- a/tests/test_send_recv_dialog_gui.py +++ b/tests/test_send_recv_dialog_gui.py @@ -8,6 +8,8 @@ from PyQt5.QtGui import QMouseEvent from PyQt5.QtTest import QTest from PyQt5.QtWidgets import QApplication + +from urh.signalprocessing.IQArray import IQArray from urh.signalprocessing.ProtocolSniffer import ProtocolSniffer from tests.QtTestCase import QtTestCase @@ -70,7 +72,7 @@ def __get_recv_dialog(self): return receive_dialog def __get_send_dialog(self): - send_dialog = SendDialog(self.form.project_manager, modulated_data=self.signal.data, + send_dialog = SendDialog(self.form.project_manager, modulated_data=self.signal.iq_array, modulation_msg_indices=None, testing_mode=True, parent=self.form) if self.SHOW: @@ -150,7 +152,7 @@ def test_receive(self): QTest.qWait(self.SEND_RECV_TIMEOUT) self.assertEqual(receive_dialog.device.current_index, 3) - self.assertTrue(np.array_equal(receive_dialog.device.data[:3], data)) + self.assertTrue(np.array_equal(receive_dialog.device.data[:3].flatten(), data.view(np.float32))) receive_dialog.ui.btnStop.click() receive_dialog.ui.btnClear.click() @@ -217,7 +219,7 @@ def test_send(self): self.assertEqual(receive_dialog.device.current_index, 2 * self.signal.num_samples) self.assertTrue(np.array_equal(receive_dialog.device.data[:receive_dialog.device.current_index // 2], - self.signal.data)) + self.signal.iq_array.data)) self.assertEqual(send_dialog.ui.lblCurrentRepeatValue.text(), "Sending finished") self.assertFalse(send_dialog.ui.btnStop.isEnabled()) @@ -234,7 +236,7 @@ def test_continuous_send_dialog(self): port = self.get_free_port() gframe = self.form.generator_tab_controller - expected = np.zeros(gframe.total_modulated_samples, dtype=np.complex64) + expected = IQArray(None, np.float32, gframe.total_modulated_samples) expected = gframe.modulate_data(expected) current_index = Value("L", 0) buffer = Array("f", 4 * len(expected)) @@ -257,9 +259,12 @@ def test_continuous_send_dialog(self): # CI sometimes swallows a sample self.assertGreaterEqual(current_index.value, len(expected) - 1) - buffer = np.frombuffer(buffer.get_obj(), dtype=np.complex64) + buffer = np.frombuffer(buffer.get_obj(), dtype=np.float32) + buffer = buffer.reshape((len(buffer) // 2, 2)) + for i in range(len(expected)): - self.assertEqual(buffer[i], expected[i], msg=str(i)) + self.assertEqual(buffer[i, 0], expected[i, 0], msg=str(i)) + self.assertEqual(buffer[i, 1], expected[i, 1], msg=str(i)) continuous_send_dialog.ui.btnStop.click() continuous_send_dialog.ui.btnClear.click() diff --git a/tests/test_signal_tab_GUI.py b/tests/test_signal_tab_GUI.py index ee868c2507..cfb736b90c 100644 --- a/tests/test_signal_tab_GUI.py +++ b/tests/test_signal_tab_GUI.py @@ -69,6 +69,7 @@ def test_graphic_view_selection(self): self.assertEqual(frame.ui.lNumSelectedSamples.text(), "4000") + frame.ui.spinBoxNoiseTreshold.setValue(1) noise_val = frame.ui.spinBoxNoiseTreshold.value() frame.ui.gvSignal.set_noise_clicked.emit() self.assertNotEqual(noise_val, frame.ui.spinBoxNoiseTreshold.value()) diff --git a/tests/test_simulator.py b/tests/test_simulator.py index ff7756a05a..c412fa3ed1 100644 --- a/tests/test_simulator.py +++ b/tests/test_simulator.py @@ -10,6 +10,7 @@ from urh.controller.SimulatorTabController import SimulatorTabController from urh.controller.dialogs.SimulatorDialog import SimulatorDialog +from urh.signalprocessing.IQArray import IQArray from urh.util.Logger import logger @@ -93,7 +94,7 @@ def test_simulation_flow(self): self.alice.send_raw_data(modulator.modulate(msg1), 1) time.sleep(self.TIMEOUT) - self.alice.send_raw_data(np.zeros(self.num_zeros_for_pause, dtype=np.complex64), 1) + self.alice.send_raw_data(IQArray(None, np.float32, self.num_zeros_for_pause), 1) while not any("Sending message 2" in msg for msg in dialog.simulator.log_messages): logger.debug("Waiting for simulator to send message 2") @@ -113,7 +114,7 @@ def test_simulation_flow(self): self.alice.send_raw_data(modulator.modulate(msg2), 1) time.sleep(self.TIMEOUT) - self.alice.send_raw_data(np.zeros(self.num_zeros_for_pause, dtype=np.complex64), 1) + self.alice.send_raw_data(IQArray(None, np.float32, self.num_zeros_for_pause), 1) while not any("Sending message 4" in msg for msg in dialog.simulator.log_messages): logger.debug("Waiting for simulator to send message 4") @@ -133,7 +134,7 @@ def test_simulation_flow(self): self.alice.send_raw_data(modulator.modulate(msg3), 1) time.sleep(self.TIMEOUT) - self.alice.send_raw_data(np.zeros(self.num_zeros_for_pause, dtype=np.complex64), 1) + self.alice.send_raw_data(IQArray(None, np.float32, self.num_zeros_for_pause), 1) while not any("Sending message 6" in msg for msg in dialog.simulator.log_messages): logger.debug("Waiting for simulator to send message 6") @@ -241,7 +242,7 @@ def test_external_program_simulator(self): self.alice.send_raw_data(modulator.modulate("100" + "10101010" * 42), 1) time.sleep(self.TIMEOUT) - self.alice.send_raw_data(np.zeros(self.num_zeros_for_pause, dtype=np.complex64), 1) + self.alice.send_raw_data(IQArray(None, np.float32, 2*self.num_zeros_for_pause), 1) while not any("Sending message" in msg for msg in dialog.simulator.log_messages): logger.debug("Waiting for simulator to send message") @@ -278,9 +279,9 @@ def __demodulate(self, connection: socket.socket): if len(total_data) == 0: logger.error("Did not receive any data from socket.") - arr = np.array(np.frombuffer(b"".join(total_data), dtype=np.complex64)) + arr = IQArray(np.array(np.frombuffer(b"".join(total_data), dtype=np.complex64))) signal = Signal("", "") - signal._fulldata = arr + signal.iq_array = arr pa = ProtocolAnalyzer(signal) pa.get_protocol_from_signal() return pa.plain_bits_str diff --git a/tests/test_simulator_dialog.py b/tests/test_simulator_dialog.py index 2d8591aaf4..198cd452f8 100644 --- a/tests/test_simulator_dialog.py +++ b/tests/test_simulator_dialog.py @@ -83,7 +83,7 @@ def test_set_sniff_parameters(self): self.assertEqual(simulator.sniffer.signal.tolerance, 13) self.__edit_spinbox_value(sniff_settings_widget.ui.spinbox_sniff_Noise, 0.1234) - self.assertEqual(simulator.sniffer.signal.noise_threshold, 0.1234) + self.assertEqual(simulator.sniffer.signal.noise_threshold_relative, 0.1234) sniff_settings_widget.ui.combox_sniff_Modulation.setCurrentText("PSK") self.assertEqual(simulator.sniffer.signal.modulation_type_str, "PSK") diff --git a/tests/test_simulator_tab_gui.py b/tests/test_simulator_tab_gui.py index d3215cb4a6..79f387b3ab 100644 --- a/tests/test_simulator_tab_gui.py +++ b/tests/test_simulator_tab_gui.py @@ -8,6 +8,8 @@ from PyQt5.QtGui import QContextMenuEvent from PyQt5.QtTest import QTest, QSignalSpy from PyQt5.QtWidgets import QApplication, QMenu, QCompleter + +from urh.signalprocessing.IQArray import IQArray from urh.util.Logger import logger from tests.QtTestCase import QtTestCase @@ -367,7 +369,7 @@ def test_open_simulator_dialog_and_send_mismatching_message(self): sender.send_raw_data(modulator.modulate("1" * 352), 1) time.sleep(0.5) - sender.send_raw_data(np.zeros(1000, dtype=np.complex64), 1) + sender.send_raw_data(IQArray(None, np.float32, 2000), 1) while not any("Waiting for message 2" in msg for msg in dialog.simulator.log_messages): logger.debug("Waiting for simulator wait for message 2") @@ -375,7 +377,7 @@ def test_open_simulator_dialog_and_send_mismatching_message(self): sender.send_raw_data(modulator.modulate("10" * 176), 1) time.sleep(0.5) - sender.send_raw_data(np.zeros(1000, dtype=np.complex64), 1) + sender.send_raw_data(IQArray(None, np.float32, 2000), 1) while not any("Mismatch for label:" in msg for msg in dialog.simulator.log_messages): logger.debug("Waiting for mismatching message") time.sleep(1) diff --git a/tests/test_spectrogram.py b/tests/test_spectrogram.py index 06dc513b8d..50653d37f4 100644 --- a/tests/test_spectrogram.py +++ b/tests/test_spectrogram.py @@ -9,7 +9,7 @@ class TestSpectrogram(QtTestCase): def setUp(self): self.signal = Signal(self.get_path_for_filename("two_participants.coco"), "test") - self.spectrogram = Spectrogram(self.signal.data) + self.spectrogram = Spectrogram(self.signal.iq_array.data) def test_create_spectrogram_image(self): image = self.spectrogram.create_spectrogram_image()