diff --git a/data/ui/signal_frame.ui b/data/ui/signal_frame.ui index 666b9629a7..4e536ab61d 100644 --- a/data/ui/signal_frame.ui +++ b/data/ui/signal_frame.ui @@ -84,7 +84,7 @@ - Signal View: + Signal view: @@ -282,7 +282,7 @@ <html><head/><body><p>For <span style=" font-weight:600;">higher order</span> modulations (&gt; 1 Bits/Symbol), there are <span style=" font-weight:600;">multiple</span> centers. We assume that the <span style=" font-weight:600;">spacing</span> between all possible symbols is <span style=" font-weight:600;">constant</span>. Therefore you configure the spacing between centers.</p></body></html> - Center Spacing: + Center spacing: @@ -351,7 +351,7 @@ <html><head/><body><p>This is the error tolerance for determining the <span style=" font-weight:600;">pulse lengths</span> in the demodulated signal.</p><p><span style=" font-weight:400; font-style:italic;">Example:</span> Say, we are reading a ones pulse and the tolerance value was set to 5. Then 5 errors (which must follow sequentially) are accepted.</p><p>Tune this value if you have <span style=" font-weight:600;">spiky data</span> after demodulation.</p></body></html> - Error Tolerance: + Error tolerance: @@ -405,7 +405,7 @@ - <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;">suppress noise</span>. All samples with a magnitude lower than this threshold 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> + <html><head/><body><p>Choose the view of your signal. Analog, Demodulated, Spectrogram or I/Q view.</p><p>The quadrature demodulation uses a <span style=" font-weight:600;">threshold of magnitudes,</span> to <span style=" font-weight:600;">suppress noise</span>. All samples with a magnitude lower than this threshold 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><p><span style=" font-weight:600;">Signal colors</span>:<br/>I =&gt; <span style=" font-weight:600; color:#3232e1;">blue</span><br/>Q =&gt; <span style=" font-weight:600;">black</span></p></body></html> @@ -422,6 +422,11 @@ Spectrogram + + + I/Q view + + @@ -545,7 +550,7 @@ If this is set to true, your selected protocol bits will show up in the signal view, and vice versa. - Sync Selection + Sync selection true @@ -574,7 +579,7 @@ If you want your protocol to be better separated, edit the PauseLen using right-click menu from a selection in SignalView or ProtocolView. - Show Signal as + Show data as @@ -660,7 +665,7 @@ If you want your protocol to be better separated, edit the PauseLen using right- - FFT Window Size: + FFT window size: diff --git a/src/urh/controller/widgets/SignalFrame.py b/src/urh/controller/widgets/SignalFrame.py index 90217e9964..cb086078a5 100644 --- a/src/urh/controller/widgets/SignalFrame.py +++ b/src/urh/controller/widgets/SignalFrame.py @@ -685,8 +685,8 @@ def on_noise_threshold_changed(self): 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: - # Draw Noise only in Analog View + if self.ui.cbSignalView.currentIndex() == 0 or self.ui.cbSignalView.currentIndex() == 3: + # Draw Noise only in analog and I/Q view self.ui.gvSignal.scene().draw_noise_area(minimum, maximum - minimum) @pyqtSlot(int) diff --git a/src/urh/settings.py b/src/urh/settings.py index 0e35a8a5d9..1419eaae5d 100644 --- a/src/urh/settings.py +++ b/src/urh/settings.py @@ -42,6 +42,8 @@ def get_qt_settings_filename(): TRANSPARENT_COLOR = QColor(Qt.transparent) LINECOLOR = QColor.fromRgb(225, 225, 225) +LINECOLOR_I = QColor.fromRgb(50, 50, 225) +LINECOLOR_Q = QColor.fromRgb(55, 53, 53) BGCOLOR = QColor.fromRgb(55, 53, 53) AXISCOLOR = QColor.fromRgb(200, 200, 200, 100) ARROWCOLOR = QColor.fromRgb(204, 120, 50) diff --git a/src/urh/signalprocessing/Signal.py b/src/urh/signalprocessing/Signal.py index 22e55bb988..0a13e37f12 100644 --- a/src/urh/signalprocessing/Signal.py +++ b/src/urh/signalprocessing/Signal.py @@ -379,6 +379,13 @@ def real_plot_data(self): except AttributeError: return np.zeros(0, dtype=np.float32) + @property + def imag_plot_data(self): + try: + return self.iq_array.imag + except AttributeError: + return np.zeros(0, dtype=np.float32) + @property def changed(self) -> bool: """ diff --git a/src/urh/ui/painting/SceneManager.py b/src/urh/ui/painting/SceneManager.py index e108d1df15..2f9c931878 100644 --- a/src/urh/ui/painting/SceneManager.py +++ b/src/urh/ui/painting/SceneManager.py @@ -28,11 +28,12 @@ def plot_data(self, value): @property def num_samples(self): + if isinstance(self.plot_data, list): + return len(self.plot_data[0]) return len(self.plot_data) def show_scene_section(self, x1: float, x2: float, subpath_ranges=None, colors=None): """ - :param x1: start of section to show :param x2: end of section to show :param subpath_ranges: for coloring subpaths @@ -44,12 +45,21 @@ def show_scene_section(self, x1: float, x2: float, subpath_ranges=None, colors=N start, end = self.__limit_value(x1), self.__limit_value(x2) if end > start: - paths = path_creator.create_path(self.plot_data, start=start, end=end, - subpath_ranges=subpath_ranges) - self.set_path(paths, colors=colors) - - def set_path(self, paths: list, colors=None): - self.clear_path() + if isinstance(self.plot_data, list): + paths_i = path_creator.create_path(self.plot_data[0], start=start, end=end, + subpath_ranges=subpath_ranges) + paths_q = path_creator.create_path(self.plot_data[1], start=start, end=end, + subpath_ranges=subpath_ranges) + self.set_path(paths_i, colors=[settings.LINECOLOR_I] * len(paths_i)) + self.set_path(paths_q, colors=[settings.LINECOLOR_Q] * len(paths_q), run_clear_path=False) + else: + paths = path_creator.create_path(self.plot_data, start=start, end=end, + subpath_ranges=subpath_ranges) + self.set_path(paths, colors=colors) + + def set_path(self, paths: list, colors=None, run_clear_path=True): + if run_clear_path: + self.clear_path() colors = [settings.LINECOLOR] * len(paths) if colors is None else colors assert len(paths) == len(colors) for path, color in zip(paths, colors): @@ -67,7 +77,10 @@ def init_scene(self): if self.num_samples == 0: return - minimum, maximum = IQArray.min_max_for_dtype(self.plot_data.dtype) + if isinstance(self.plot_data, list): + minimum, maximum = IQArray.min_max_for_dtype(self.plot_data[0].dtype) + else: + minimum, maximum = IQArray.min_max_for_dtype(self.plot_data.dtype) self.scene.setSceneRect(0, minimum, self.num_samples, maximum - minimum) self.scene.setBackgroundBrush(settings.BGCOLOR) diff --git a/src/urh/ui/painting/SignalSceneManager.py b/src/urh/ui/painting/SignalSceneManager.py index 5fcc501126..d0a312a458 100644 --- a/src/urh/ui/painting/SignalSceneManager.py +++ b/src/urh/ui/painting/SignalSceneManager.py @@ -8,17 +8,24 @@ class SignalSceneManager(SceneManager): def __init__(self, signal: Signal, parent): super().__init__(parent) self.signal = signal - self.scene_type = 0 # 0 = Analog Signal, 1 = QuadDemodView + self.scene_type = 0 # 0 = Analog Signal, 1 = QuadDemodView, 2 = Spectogram, 3 = I/Q view self.mod_type = "ASK" def show_scene_section(self, x1: float, x2: float, subpath_ranges=None, colors=None): - self.plot_data = self.signal.real_plot_data if self.scene_type == 0 else self.signal.qad + if self.scene_type == 0: + self.plot_data = self.signal.real_plot_data + elif self.scene_type == 3: + self.plot_data = [self.signal.imag_plot_data, self.signal.real_plot_data] + else: + self.plot_data = self.signal.qad super().show_scene_section(x1, x2, subpath_ranges=subpath_ranges, colors=colors) def init_scene(self): if self.scene_type == 0: # Ensure real plot has same y Axis self.plot_data = self.signal.real_plot_data + elif self.scene_type == 3: + self.plot_data = [self.signal.imag_plot_data, self.signal.real_plot_data] else: self.plot_data = self.signal.qad @@ -28,7 +35,7 @@ def init_scene(self): self.line_item.setLine(0, 0, 0, 0) # Hide Axis - if self.scene_type == 0: + if self.scene_type == 0 or self.scene_type == 3: self.scene.draw_noise_area(self.signal.noise_min_plot, self.signal.noise_max_plot - self.signal.noise_min_plot) else: self.scene.draw_sep_area(-self.signal.center_thresholds) diff --git a/src/urh/ui/ui_signal_frame.py b/src/urh/ui/ui_signal_frame.py index b1937c6bab..6f172b4b72 100644 --- a/src/urh/ui/ui_signal_frame.py +++ b/src/urh/ui/ui_signal_frame.py @@ -2,7 +2,8 @@ # # -# WARNING! All changes made in this file will be lost! +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. from PyQt5 import QtCore, QtGui, QtWidgets @@ -165,6 +166,7 @@ def setupUi(self, SignalFrame): self.cbSignalView.addItem("") self.cbSignalView.addItem("") self.cbSignalView.addItem("") + self.cbSignalView.addItem("") self.gridLayout_2.addWidget(self.cbSignalView, 17, 1, 1, 1) self.spinBoxNoiseTreshold = QtWidgets.QDoubleSpinBox(SignalFrame) self.spinBoxNoiseTreshold.setSuffix("") @@ -566,7 +568,7 @@ def setupUi(self, SignalFrame): def retranslateUi(self, SignalFrame): _translate = QtCore.QCoreApplication.translate SignalFrame.setWindowTitle(_translate("SignalFrame", "Frame")) - self.lSignalViewText.setText(_translate("SignalFrame", "Signal View:")) + self.lSignalViewText.setText(_translate("SignalFrame", "Signal view:")) self.spinBoxBitsPerSymbol.setToolTip(_translate("SignalFrame", "

Higher order modulations can carry multiple bits with each symbol. Configure how many bits are represented by a symbol. (Default = Binary modulation with one bit per symbol)

")) self.btnSaveSignal.setText(_translate("SignalFrame", "...")) self.btnCloseSignal.setText(_translate("SignalFrame", "X")) @@ -578,20 +580,21 @@ def retranslateUi(self, SignalFrame): self.cbProtoView.setItemText(1, _translate("SignalFrame", "Hex")) self.cbProtoView.setItemText(2, _translate("SignalFrame", "ASCII")) self.lCenterSpacing.setToolTip(_translate("SignalFrame", "

For higher order modulations (> 1 Bits/Symbol), there are multiple centers. We assume that the spacing between all possible symbols is constant. Therefore you configure the spacing between centers.

")) - self.lCenterSpacing.setText(_translate("SignalFrame", "Center Spacing:")) + self.lCenterSpacing.setText(_translate("SignalFrame", "Center spacing:")) self.spinBoxTolerance.setToolTip(_translate("SignalFrame", "

This is the error tolerance for determining the pulse lengths in the demodulated signal.

Example: Say, we are reading a ones pulse and the tolerance value was set to 5. Then 5 errors (which must follow sequentially) are accepted.

Tune this value if you have spiky data after demodulation.

")) self.lSamplesPerSymbol.setToolTip(_translate("SignalFrame", "

This is the length of one symbol in samples. For binary modulations (default) this is the bit length.

")) self.lSamplesPerSymbol.setText(_translate("SignalFrame", "Samples/Symbol:")) self.lErrorTolerance.setToolTip(_translate("SignalFrame", "

This is the error tolerance for determining the pulse lengths in the demodulated signal.

Example: Say, we are reading a ones pulse and the tolerance value was set to 5. Then 5 errors (which must follow sequentially) are accepted.

Tune this value if you have spiky data after demodulation.

")) - self.lErrorTolerance.setText(_translate("SignalFrame", "Error Tolerance:")) + self.lErrorTolerance.setText(_translate("SignalFrame", "Error tolerance:")) 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.labelModulation.setToolTip(_translate("SignalFrame", "

Choose signals modulation:

")) self.labelModulation.setText(_translate("SignalFrame", "Modulation:")) - self.cbSignalView.setToolTip(_translate("SignalFrame", "

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

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

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

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

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

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

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

Signal colors:
I => blue
Q => black

")) self.cbSignalView.setItemText(0, _translate("SignalFrame", "Analog")) self.cbSignalView.setItemText(1, _translate("SignalFrame", "Demodulated")) self.cbSignalView.setItemText(2, _translate("SignalFrame", "Spectrogram")) + self.cbSignalView.setItemText(3, _translate("SignalFrame", "I/Q view")) self.spinBoxNoiseTreshold.setToolTip(_translate("SignalFrame", "

Set the noise magnitude of your signal. You can tune this value to mute noise in your signal and reveal the true data.

")) self.spinBoxCenterSpacing.setToolTip(_translate("SignalFrame", "

For higher order modulations (> 1 Bits/Symbol), there are multiple centers. We assume that the spacing between all possible symbols is constant. Therefore you configure the spacing between centers.

")) self.lineEditSignalName.setText(_translate("SignalFrame", "SignalName")) @@ -601,17 +604,17 @@ def retranslateUi(self, SignalFrame): self.cbModulationType.setItemText(2, _translate("SignalFrame", "PSK")) self.btnAdvancedModulationSettings.setText(_translate("SignalFrame", "...")) self.chkBoxSyncSelection.setToolTip(_translate("SignalFrame", "If this is set to true, your selected protocol bits will show up in the signal view, and vice versa.")) - self.chkBoxSyncSelection.setText(_translate("SignalFrame", "Sync Selection")) + self.chkBoxSyncSelection.setText(_translate("SignalFrame", "Sync selection")) self.labelSpectrogramMin.setText(_translate("SignalFrame", "Datamin:")) self.labelSpectrogramMax.setText(_translate("SignalFrame", "Datamax:")) self.chkBoxShowProtocol.setToolTip(_translate("SignalFrame", "Show the extracted protocol based on the parameters InfoLen, PauseLen and ZeroTreshold (in QuadratureDemod-View).\n" "\n" "If you want your protocol to be better separated, edit the PauseLen using right-click menu from a selection in SignalView or ProtocolView.")) - self.chkBoxShowProtocol.setText(_translate("SignalFrame", "Show Signal as")) + self.chkBoxShowProtocol.setText(_translate("SignalFrame", "Show data as")) self.spinBoxSamplesPerSymbol.setToolTip(_translate("SignalFrame", "

This is the length of one symbol in samples. For binary modulations (default) this is the bit length.

")) self.btnAutoDetect.setToolTip(_translate("SignalFrame", "

Automatically detect center, bit length and tolerance. You can also choose to additionally detect the noise and modulation when clicking this button.

")) self.btnAutoDetect.setText(_translate("SignalFrame", "Autodetect parameters")) - self.labelFFTWindowSize.setText(_translate("SignalFrame", "FFT Window Size:")) + self.labelFFTWindowSize.setText(_translate("SignalFrame", "FFT window size:")) 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.labelNoise.setToolTip(_translate("SignalFrame", "

Set the noise magnitude of your signal. You can tune this value to mute noise in your signal and reveal the true data.

")) self.labelNoise.setText(_translate("SignalFrame", "Noise:"))