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 @@
00
- 803
+ 814822
@@ -21,58 +21,95 @@
- 1
+ 0Generation
-
-
-
- 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 @@
00
- 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 @@
00860
- 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 @@
00482
- 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 @@
001057
- 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:
- (