diff --git a/qspectrumanalyzer/__main__.py b/qspectrumanalyzer/__main__.py
index 87c7c21..5423f6f 100755
--- a/qspectrumanalyzer/__main__.py
+++ b/qspectrumanalyzer/__main__.py
@@ -6,6 +6,7 @@ from PyQt4 import QtCore, QtGui
from qspectrumanalyzer.version import __version__
from qspectrumanalyzer.backend import RtlPowerThread, RtlPowerFftwThread
+from qspectrumanalyzer.data import DataStorage
from qspectrumanalyzer.plot import SpectrumPlotWidget, WaterfallPlotWidget
from qspectrumanalyzer.utils import color_to_str, str_to_color
@@ -124,7 +125,8 @@ class QSpectrumAnalyzerColors(QtGui.QDialog, Ui_QSpectrumAnalyzerColors):
# Load settings
settings = QtCore.QSettings()
self.mainColorButton.setColor(str_to_color(settings.value("main_color", "255, 255, 0, 255")))
- self.peakHoldColorButton.setColor(str_to_color(settings.value("peak_hold_color", "255, 0, 0, 255")))
+ self.peakHoldMaxColorButton.setColor(str_to_color(settings.value("peak_hold_max_color", "255, 0, 0, 255")))
+ self.peakHoldMinColorButton.setColor(str_to_color(settings.value("peak_hold_min_color", "0, 0, 255, 255")))
self.averageColorButton.setColor(str_to_color(settings.value("average_color", "0, 255, 255, 255")))
self.persistenceColorButton.setColor(str_to_color(settings.value("persistence_color", "0, 255, 0, 255")))
@@ -132,7 +134,8 @@ class QSpectrumAnalyzerColors(QtGui.QDialog, Ui_QSpectrumAnalyzerColors):
"""Save settings when dialog is accepted"""
settings = QtCore.QSettings()
settings.setValue("main_color", color_to_str(self.mainColorButton.color()))
- settings.setValue("peak_hold_color", color_to_str(self.peakHoldColorButton.color()))
+ settings.setValue("peak_hold_max_color", color_to_str(self.peakHoldMaxColorButton.color()))
+ settings.setValue("peak_hold_min_color", color_to_str(self.peakHoldMinColorButton.color()))
settings.setValue("average_color", color_to_str(self.averageColorButton.color()))
settings.setValue("persistence_color", color_to_str(self.persistenceColorButton.color()))
QtGui.QDialog.accept(self)
@@ -145,11 +148,6 @@ class QSpectrumAnalyzerMainWindow(QtGui.QMainWindow, Ui_QSpectrumAnalyzerMainWin
super().__init__(parent)
self.setupUi(self)
- # Setup rtl_power thread and connect signals
- self.prev_data_timestamp = None
- self.rtl_power_thread = None
- self.setup_rtl_power_thread()
-
# Create plot widgets and update UI
self.spectrumPlotWidget = SpectrumPlotWidget(self.mainPlotLayout)
self.waterfallPlotWidget = WaterfallPlotWidget(self.waterfallPlotLayout, self.histogramPlotLayout)
@@ -157,6 +155,12 @@ class QSpectrumAnalyzerMainWindow(QtGui.QMainWindow, Ui_QSpectrumAnalyzerMainWin
# Link main spectrum plot to waterfall plot
self.spectrumPlotWidget.plot.setXLink(self.waterfallPlotWidget.plot)
+ # Setup rtl_power thread and connect signals
+ self.prev_data_timestamp = None
+ self.data_storage = None
+ self.rtl_power_thread = None
+ self.setup_rtl_power_thread()
+
self.update_buttons()
self.load_settings()
@@ -166,13 +170,23 @@ class QSpectrumAnalyzerMainWindow(QtGui.QMainWindow, Ui_QSpectrumAnalyzerMainWin
self.stop()
settings = QtCore.QSettings()
+ self.data_storage = DataStorage(max_history_size=settings.value("waterfall_history_size", 100, int))
+ self.data_storage.data_updated.connect(self.update_data)
+ self.data_storage.data_updated.connect(self.spectrumPlotWidget.update_plot)
+ self.data_storage.data_updated.connect(self.spectrumPlotWidget.update_persistence)
+ self.data_storage.data_recalculated.connect(self.spectrumPlotWidget.recalculate_plot)
+ self.data_storage.data_recalculated.connect(self.spectrumPlotWidget.recalculate_persistence)
+ self.data_storage.history_updated.connect(self.waterfallPlotWidget.update_plot)
+ self.data_storage.average_updated.connect(self.spectrumPlotWidget.update_average)
+ self.data_storage.peak_hold_max_updated.connect(self.spectrumPlotWidget.update_peak_hold_max)
+ self.data_storage.peak_hold_min_updated.connect(self.spectrumPlotWidget.update_peak_hold_min)
+
backend = settings.value("backend", "rtl_power")
if backend == "rtl_power_fftw":
- self.rtl_power_thread = RtlPowerFftwThread()
+ self.rtl_power_thread = RtlPowerFftwThread(self.data_storage)
else:
- self.rtl_power_thread = RtlPowerThread()
+ self.rtl_power_thread = RtlPowerThread(self.data_storage)
- self.rtl_power_thread.dataUpdated.connect(self.update_data)
self.rtl_power_thread.rtlPowerStarted.connect(self.update_buttons)
self.rtl_power_thread.rtlPowerStopped.connect(self.update_buttons)
@@ -212,7 +226,8 @@ class QSpectrumAnalyzerMainWindow(QtGui.QMainWindow, Ui_QSpectrumAnalyzerMainWin
self.ppmSpinBox.setValue(settings.value("ppm", 0, int))
self.cropSpinBox.setValue(settings.value("crop", 0, int))
self.mainCurveCheckBox.setChecked(settings.value("main_curve", 1, int))
- self.peakHoldCheckBox.setChecked(settings.value("peak_hold", 0, int))
+ self.peakHoldMaxCheckBox.setChecked(settings.value("peak_hold_max", 0, int))
+ self.peakHoldMinCheckBox.setChecked(settings.value("peak_hold_min", 0, int))
self.averageCheckBox.setChecked(settings.value("average", 0, int))
self.smoothCheckBox.setChecked(settings.value("smooth", 0, int))
self.persistenceCheckBox.setChecked(settings.value("persistence", 0, int))
@@ -250,7 +265,8 @@ class QSpectrumAnalyzerMainWindow(QtGui.QMainWindow, Ui_QSpectrumAnalyzerMainWin
settings.setValue("ppm", self.ppmSpinBox.value())
settings.setValue("crop", self.cropSpinBox.value())
settings.setValue("main_curve", int(self.mainCurveCheckBox.isChecked()))
- settings.setValue("peak_hold", int(self.peakHoldCheckBox.isChecked()))
+ settings.setValue("peak_hold_max", int(self.peakHoldMaxCheckBox.isChecked()))
+ settings.setValue("peak_hold_min", int(self.peakHoldMinCheckBox.isChecked()))
settings.setValue("average", int(self.averageCheckBox.isChecked()))
settings.setValue("smooth", int(self.smoothCheckBox.isChecked()))
settings.setValue("persistence", int(self.persistenceCheckBox.isChecked()))
@@ -270,45 +286,55 @@ class QSpectrumAnalyzerMainWindow(QtGui.QMainWindow, Ui_QSpectrumAnalyzerMainWin
self.singleShotButton.setEnabled(not self.rtl_power_thread.alive)
self.stopButton.setEnabled(self.rtl_power_thread.alive)
- def update_data(self, data):
- """Update plots when new data is received"""
- self.waterfallPlotWidget.update_plot(data)
- self.spectrumPlotWidget.update_plot(data)
-
+ def update_data(self, data_storage):
+ """Update GUI when new data is received"""
# Show number of hops and how much time did the sweep really take
timestamp = time.time()
- self.show_status(self.tr("Frequency hops: {} Sweep time: {:.2f} s").format(
- self.rtl_power_thread.params["hops"] or self.tr("N/A"),
- timestamp - self.prev_data_timestamp
- ), timeout=0)
+ sweep_time = timestamp - self.prev_data_timestamp
self.prev_data_timestamp = timestamp
+ self.show_status(
+ self.tr("Frequency hops: {} | Sweep time: {:.2f} s | FPS: {:.2f}").format(
+ self.rtl_power_thread.params["hops"] or self.tr("N/A"),
+ sweep_time,
+ 1 / sweep_time
+ ),
+ timeout=0
+ )
+
def start(self, single_shot=False):
"""Start rtl_power thread"""
settings = QtCore.QSettings()
self.prev_data_timestamp = time.time()
- self.waterfallPlotWidget.counter = 0
- self.waterfallPlotWidget.history_size = settings.value("waterfall_history_size", 100, int)
+ self.data_storage.reset()
+ self.data_storage.set_smooth(
+ bool(self.smoothCheckBox.isChecked()),
+ settings.value("smooth_length", 11, int),
+ settings.value("smooth_window", "hanning"),
+ recalculate=False
+ )
+
+ self.waterfallPlotWidget.history_size = settings.value("waterfall_history_size", 100, int)
+ self.waterfallPlotWidget.clear_plot()
- self.spectrumPlotWidget.counter = 0
self.spectrumPlotWidget.main_curve = bool(self.mainCurveCheckBox.isChecked())
self.spectrumPlotWidget.main_color = str_to_color(settings.value("main_color", "255, 255, 0, 255"))
- self.spectrumPlotWidget.peak_hold = bool(self.peakHoldCheckBox.isChecked())
- self.spectrumPlotWidget.peak_hold_color = str_to_color(settings.value("peak_hold_color", "255, 0, 0, 255"))
+ self.spectrumPlotWidget.peak_hold_max = bool(self.peakHoldMaxCheckBox.isChecked())
+ self.spectrumPlotWidget.peak_hold_max_color = str_to_color(settings.value("peak_hold_max_color", "255, 0, 0, 255"))
+ self.spectrumPlotWidget.peak_hold_min = bool(self.peakHoldMinCheckBox.isChecked())
+ self.spectrumPlotWidget.peak_hold_min_color = str_to_color(settings.value("peak_hold_min_color", "0, 0, 255, 255"))
self.spectrumPlotWidget.average = bool(self.averageCheckBox.isChecked())
self.spectrumPlotWidget.average_color = str_to_color(settings.value("average_color", "0, 255, 255, 255"))
self.spectrumPlotWidget.persistence = bool(self.persistenceCheckBox.isChecked())
self.spectrumPlotWidget.persistence_length = settings.value("persistence_length", 5, int)
self.spectrumPlotWidget.persistence_decay = settings.value("persistence_decay", "exponential")
self.spectrumPlotWidget.persistence_color = str_to_color(settings.value("persistence_color", "0, 255, 0, 255"))
- self.spectrumPlotWidget.smooth = bool(self.smoothCheckBox.isChecked())
- self.spectrumPlotWidget.smooth_length = settings.value("smooth_length", 11, int)
- self.spectrumPlotWidget.smooth_window = settings.value("smooth_window", "hanning")
- self.spectrumPlotWidget.main_clear()
- self.spectrumPlotWidget.peak_hold_clear()
- self.spectrumPlotWidget.average_clear()
- self.spectrumPlotWidget.persistence_clear()
+ self.spectrumPlotWidget.clear_plot()
+ self.spectrumPlotWidget.clear_peak_hold_max()
+ self.spectrumPlotWidget.clear_peak_hold_min()
+ self.spectrumPlotWidget.clear_average()
+ self.spectrumPlotWidget.clear_persistence()
if not self.rtl_power_thread.alive:
self.rtl_power_thread.setup(float(self.startFreqSpinBox.value()),
@@ -342,46 +368,60 @@ class QSpectrumAnalyzerMainWindow(QtGui.QMainWindow, Ui_QSpectrumAnalyzerMainWin
@QtCore.pyqtSlot(bool)
def on_mainCurveCheckBox_toggled(self, checked):
self.spectrumPlotWidget.main_curve = checked
- if not checked:
- self.spectrumPlotWidget.main_clear()
+ if self.spectrumPlotWidget.curve.xData is None:
+ self.spectrumPlotWidget.update_plot(self.data_storage)
+ self.spectrumPlotWidget.curve.setVisible(checked)
@QtCore.pyqtSlot(bool)
- def on_peakHoldCheckBox_toggled(self, checked):
- self.spectrumPlotWidget.peak_hold = checked
- if not checked:
- self.spectrumPlotWidget.peak_hold_clear()
+ def on_peakHoldMaxCheckBox_toggled(self, checked):
+ self.spectrumPlotWidget.peak_hold_max = checked
+ if self.spectrumPlotWidget.curve_peak_hold_max.xData is None:
+ self.spectrumPlotWidget.update_peak_hold_max(self.data_storage)
+ self.spectrumPlotWidget.curve_peak_hold_max.setVisible(checked)
+
+ @QtCore.pyqtSlot(bool)
+ def on_peakHoldMinCheckBox_toggled(self, checked):
+ self.spectrumPlotWidget.peak_hold_min = checked
+ if self.spectrumPlotWidget.curve_peak_hold_min.xData is None:
+ self.spectrumPlotWidget.update_peak_hold_min(self.data_storage)
+ self.spectrumPlotWidget.curve_peak_hold_min.setVisible(checked)
@QtCore.pyqtSlot(bool)
def on_averageCheckBox_toggled(self, checked):
self.spectrumPlotWidget.average = checked
- if not checked:
- self.spectrumPlotWidget.average_clear()
-
- @QtCore.pyqtSlot(bool)
- def on_smoothCheckBox_toggled(self, checked):
- self.spectrumPlotWidget.smooth = checked
- self.spectrumPlotWidget.main_clear()
- self.spectrumPlotWidget.peak_hold_clear()
- self.spectrumPlotWidget.average_clear()
- self.spectrumPlotWidget.persistence_clear()
+ if self.spectrumPlotWidget.curve_average.xData is None:
+ self.spectrumPlotWidget.update_average(self.data_storage)
+ self.spectrumPlotWidget.curve_average.setVisible(checked)
@QtCore.pyqtSlot(bool)
def on_persistenceCheckBox_toggled(self, checked):
self.spectrumPlotWidget.persistence = checked
- if not checked:
- self.spectrumPlotWidget.persistence_clear()
+ if self.spectrumPlotWidget.persistence_curves[0].xData is None:
+ self.spectrumPlotWidget.recalculate_persistence(self.data_storage)
+ for curve in self.spectrumPlotWidget.persistence_curves:
+ curve.setVisible(checked)
+
+ @QtCore.pyqtSlot(bool)
+ def on_smoothCheckBox_toggled(self, checked):
+ settings = QtCore.QSettings()
+ self.data_storage.set_smooth(
+ checked,
+ settings.value("smooth_length", 11, int),
+ settings.value("smooth_window", "hanning"),
+ recalculate=True
+ )
@QtCore.pyqtSlot()
def on_smoothButton_clicked(self):
dialog = QSpectrumAnalyzerSmooth(self)
if dialog.exec_():
settings = QtCore.QSettings()
- self.spectrumPlotWidget.smooth_length = settings.value("smooth_length", 11, int)
- self.spectrumPlotWidget.smooth_window = settings.value("smooth_window", "hanning")
- self.spectrumPlotWidget.main_clear()
- self.spectrumPlotWidget.peak_hold_clear()
- self.spectrumPlotWidget.average_clear()
- self.spectrumPlotWidget.persistence_clear()
+ self.data_storage.set_smooth(
+ bool(self.smoothCheckBox.isChecked()),
+ settings.value("smooth_length", 11, int),
+ settings.value("smooth_window", "hanning"),
+ recalculate=True
+ )
@QtCore.pyqtSlot()
def on_persistenceButton_clicked(self):
@@ -397,7 +437,7 @@ class QSpectrumAnalyzerMainWindow(QtGui.QMainWindow, Ui_QSpectrumAnalyzerMainWin
if persistence_length == prev_persistence_length:
self.spectrumPlotWidget.set_colors()
else:
- self.spectrumPlotWidget.persistence_clear()
+ self.spectrumPlotWidget.recalculate_persistence(self.data_storage)
@QtCore.pyqtSlot()
def on_colorsButton_clicked(self):
@@ -405,7 +445,8 @@ class QSpectrumAnalyzerMainWindow(QtGui.QMainWindow, Ui_QSpectrumAnalyzerMainWin
if dialog.exec_():
settings = QtCore.QSettings()
self.spectrumPlotWidget.main_color = str_to_color(settings.value("main_color", "255, 255, 0, 255"))
- self.spectrumPlotWidget.peak_hold_color = str_to_color(settings.value("peak_hold_color", "255, 0, 0, 255"))
+ self.spectrumPlotWidget.peak_hold_max_color = str_to_color(settings.value("peak_hold_max_color", "255, 0, 0, 255"))
+ self.spectrumPlotWidget.peak_hold_min_color = str_to_color(settings.value("peak_hold_min_color", "0, 0, 255, 255"))
self.spectrumPlotWidget.average_color = str_to_color(settings.value("average_color", "0, 255, 255, 255"))
self.spectrumPlotWidget.persistence_color = str_to_color(settings.value("persistence_color", "0, 255, 0, 255"))
self.spectrumPlotWidget.set_colors()
diff --git a/qspectrumanalyzer/backend.py b/qspectrumanalyzer/backend.py
index 69e883e..7a8492b 100644
--- a/qspectrumanalyzer/backend.py
+++ b/qspectrumanalyzer/backend.py
@@ -6,12 +6,12 @@ from PyQt4 import QtCore
class RtlPowerBaseThread(QtCore.QThread):
"""Thread which runs rtl_power process"""
- dataUpdated = QtCore.pyqtSignal(object)
rtlPowerStarted = QtCore.pyqtSignal()
rtlPowerStopped = QtCore.pyqtSignal()
- def __init__(self, parent=None):
+ def __init__(self, data_storage, parent=None):
super().__init__(parent)
+ self.data_storage = data_storage
self.alive = False
self.process = None
@@ -137,7 +137,7 @@ class RtlPowerThread(RtlPowerBaseThread):
# This have to be stupid like this to be compatible with old broken version of rtl_power. Right way is:
# if stop_freq == self.params["stop_freq"] * 1e6:
if stop_freq > (self.params["stop_freq"] * 1e6) - step:
- self.dataUpdated.emit(self.databuffer)
+ self.data_storage.update(self.databuffer)
class RtlPowerFftwThread(RtlPowerBaseThread):
@@ -227,7 +227,7 @@ class RtlPowerFftwThread(RtlPowerBaseThread):
# Two empty lines => new set
elif not line and not self.prev_line:
self.hop = 0
- self.dataUpdated.emit(self.databuffer)
+ self.data_storage.update(self.databuffer)
self.databuffer = {"timestamp": [], "x": [], "y": []}
# Get timestamp for new hop and set
diff --git a/qspectrumanalyzer/data.py b/qspectrumanalyzer/data.py
new file mode 100644
index 0000000..0f600e6
--- /dev/null
+++ b/qspectrumanalyzer/data.py
@@ -0,0 +1,233 @@
+import time, sys
+
+from PyQt4 import QtCore
+import numpy as np
+
+from qspectrumanalyzer.utils import smooth
+
+
+class HistoryBuffer:
+ """Fixed-size NumPy array ring buffer"""
+ def __init__(self, data_size, max_history_size, dtype=float):
+ self.data_size = data_size
+ self.max_history_size = max_history_size
+ self.history_size = 0
+ self.counter = 0
+ self.buffer = np.empty(shape=(max_history_size, data_size), dtype=dtype)
+
+ def append(self, data):
+ """Append new data to ring buffer"""
+ self.counter += 1
+ if self.history_size < self.max_history_size:
+ self.history_size += 1
+ self.buffer = np.roll(self.buffer, -1, axis=0)
+ self.buffer[-1] = data
+
+ def get_buffer(self):
+ """Return buffer stripped to size of actual data"""
+ if self.history_size < self.max_history_size:
+ return self.buffer[-self.history_size:]
+ else:
+ return self.buffer
+
+ def __getitem__(self, key):
+ return self.buffer[key]
+
+
+class TaskSignals(QtCore.QObject):
+ """Task signals emitter"""
+ result = QtCore.pyqtSignal(object)
+
+
+class Task(QtCore.QRunnable):
+ """Threaded task (run it with QThreadPool worker threads)"""
+ def __init__(self, task, *args, **kwargs):
+ super().__init__()
+ self.task = task
+ self.args = args
+ self.kwargs = kwargs
+ self.signals = TaskSignals()
+
+ def run(self):
+ """Run task in worker thread and emit signal with result"""
+ #print('Running', self.task, 'in thread', QtCore.QThread.currentThreadId())
+ result = self.task(*self.args, **self.kwargs)
+ self.signals.result.emit(result)
+
+
+class DataStorage(QtCore.QObject):
+ """Data storage for spectrum measurements"""
+ history_updated = QtCore.pyqtSignal(object)
+ data_updated = QtCore.pyqtSignal(object)
+ data_recalculated = QtCore.pyqtSignal(object)
+ average_updated = QtCore.pyqtSignal(object)
+ peak_hold_max_updated = QtCore.pyqtSignal(object)
+ peak_hold_min_updated = QtCore.pyqtSignal(object)
+
+ def __init__(self, max_history_size=100, parent=None):
+ super().__init__(parent)
+ self.max_history_size = max_history_size
+ self.smooth = False
+ self.smooth_length = 11
+ self.smooth_window = "hanning"
+
+ # Use only one worker thread because it is not faster
+ # with more threads (and memory consumption is much higher)
+ self.threadpool = QtCore.QThreadPool()
+ self.threadpool.setMaxThreadCount(1)
+
+ self.reset()
+
+ def reset(self):
+ """Reset all data"""
+ self.wait()
+ self.x = None
+ self.history = None
+ self.reset_data()
+
+ def reset_data(self):
+ """Reset current data"""
+ self.wait()
+ self.y = None
+ self.average_counter = 0
+ self.average = None
+ self.peak_hold_max = None
+ self.peak_hold_min = None
+
+ def start_task(self, fn, *args, **kwargs):
+ """Run function asynchronously in worker thread"""
+ task = Task(fn, *args, **kwargs)
+ self.threadpool.start(task)
+
+ def wait(self):
+ """Wait for worker threads to complete all running tasks"""
+ self.threadpool.waitForDone()
+
+ def update(self, data):
+ """Update data storage"""
+ self.average_counter += 1
+
+ if self.x is None:
+ self.x = data["x"]
+
+ self.start_task(self.update_history, data.copy())
+ self.start_task(self.update_data, data)
+
+ def update_data(self, data):
+ """Update main spectrum data (and possibly apply smoothing)"""
+ if self.smooth:
+ data["y"] = self.smooth_data(data["y"])
+
+ self.y = data["y"]
+ self.data_updated.emit(self)
+
+ self.start_task(self.update_average, data)
+ self.start_task(self.update_peak_hold_max, data)
+ self.start_task(self.update_peak_hold_min, data)
+
+ def update_history(self, data):
+ """Update spectrum measurements history"""
+ if self.history is None:
+ self.history = HistoryBuffer(len(data["y"]), self.max_history_size)
+
+ self.history.append(data["y"])
+ self.history_updated.emit(self)
+
+ def update_average(self, data):
+ """Update average data"""
+ if self.average is None:
+ self.average = data["y"].copy()
+ else:
+ self.average = np.average((self.average, data["y"]), axis=0, weights=(self.average_counter - 1, 1))
+ self.average_updated.emit(self)
+
+ def update_peak_hold_max(self, data):
+ """Update max. peak hold data"""
+ if self.peak_hold_max is None:
+ self.peak_hold_max = data["y"].copy()
+ else:
+ self.peak_hold_max = np.maximum(self.peak_hold_max, data["y"])
+ self.peak_hold_max_updated.emit(self)
+
+ def update_peak_hold_min(self, data):
+ """Update min. peak hold data"""
+ if self.peak_hold_min is None:
+ self.peak_hold_min = data["y"].copy()
+ else:
+ self.peak_hold_min = np.minimum(self.peak_hold_min, data["y"])
+ self.peak_hold_min_updated.emit(self)
+
+ def smooth_data(self, y):
+ """Apply smoothing function to data"""
+ return smooth(y, window_len=self.smooth_length, window=self.smooth_window)
+
+ def set_smooth(self, toggle, length=11, window="hanning", recalculate=False):
+ """Toggle smoothing and set smoothing params"""
+ if toggle != self.smooth or length != self.smooth_length or window != self.smooth_window:
+ self.smooth = toggle
+ self.smooth_length = length
+ self.smooth_window = window
+ if recalculate:
+ self.start_task(self.recalculate_data)
+ else:
+ self.reset_data()
+
+ def recalculate_data(self):
+ """Recalculate current data from history"""
+ if self.history is None:
+ return
+
+ history = self.history.get_buffer()
+ if self.smooth:
+ self.y = self.smooth_data(history[-1])
+ self.average_counter = 0
+ self.average = self.y.copy()
+ self.peak_hold_max = self.y.copy()
+ self.peak_hold_min = self.y.copy()
+ for y in history[:-1]:
+ self.average_counter += 1
+ y = self.smooth_data(y)
+ self.average = np.average((self.average, y), axis=0, weights=(self.average_counter - 1, 1))
+ self.peak_hold_max = np.maximum(self.peak_hold_max, y)
+ self.peak_hold_min = np.minimum(self.peak_hold_min, y)
+ else:
+ self.y = history[-1]
+ self.average_counter = self.history.history_size
+ self.average = np.average(history, axis=0)
+ self.peak_hold_max = history.max(axis=0)
+ self.peak_hold_min = history.min(axis=0)
+
+ self.data_recalculated.emit(self)
+ #self.data_updated.emit({"x": self.x, "y": self.y})
+ #self.average_updated.emit({"x": self.x, "y": self.average})
+ #self.peak_hold_max_updated.emit({"x": self.x, "y": self.peak_hold_max})
+ #self.peak_hold_min_updated.emit({"x": self.x, "y": self.peak_hold_min})
+
+
+class Test:
+ """Test data storage performance"""
+ def __init__(self, data_size=100000, max_history_size=100):
+ self.data_size = data_size
+ self.data = {"x": np.arange(data_size),
+ "y": None}
+ self.datastorage = DataStorage(max_history_size)
+
+ def run_one(self):
+ """Generate random data and update data storage"""
+ self.data["y"] = np.random.normal(size=self.data_size)
+ self.datastorage.update(self.data)
+
+ def run(self, runs=1000):
+ """Run performance test"""
+ t = time.time()
+ for i in range(runs):
+ self.run_one()
+ self.datastorage.wait()
+ total_time = time.time() - t
+ print("Total time:", total_time)
+ print("FPS:", runs / total_time)
+
+
+if __name__ == "__main__":
+ test = Test(int(sys.argv[1]), int(sys.argv[2]))
+ test.run(int(sys.argv[3]))
diff --git a/qspectrumanalyzer/languages/qspectrumanalyzer_cs.ts b/qspectrumanalyzer/languages/qspectrumanalyzer_cs.ts
index 96cfa2e..642818a 100644
--- a/qspectrumanalyzer/languages/qspectrumanalyzer_cs.ts
+++ b/qspectrumanalyzer/languages/qspectrumanalyzer_cs.ts
@@ -3,213 +3,223 @@
QSpectrumAnalyzerColors
-
+
Colors - QSpectrumAnalyzer
-
+
Main curve color:
-
+
...
-
- Peak hold color:
-
-
-
-
+
Average color:
-
+
Persistence color:
+
+
+ Max. peak hold color:
+
+
+
+
+ Min. peak hold color:
+
+
QSpectrumAnalyzerMainWindow
-
+
QSpectrumAnalyzer
-
+
Settings
-
+
MHz
-
+
kHz
-
+
auto
-
+
Frequency
-
+
Controls
-
+
Levels
-
+
QSpectrumAnalyzer {}
-
+
&File
-
+
&Help
-
+
&Start
-
+
S&top
-
+
Si&ngle shot
-
+
&Settings...
-
+
&Quit
-
+
Ctrl+Q
-
+
&About
-
+
Interval [s]:
-
+
Gain [dB]:
-
+
Corr. [ppm]:
-
+
Crop [%]:
-
+
Start:
-
+
Stop:
-
+
Bin size:
-
- Peak hold
-
-
-
-
+
Smoothing
-
+
...
-
+
N/A
-
- Frequency hops: {} Sweep time: {:.2f} s
-
-
-
-
+
About - QSpectrumAnalyzer
-
+
Persistence
-
+
Average
-
+
Colors...
-
+
Main curve
+
+
+ Frequency hops: {} | Sweep time: {:.2f} s | FPS: {:.2f}
+
+
+
+
+ Max. hold
+
+
+
+
+ Min. hold
+
+
QSpectrumAnalyzerPersistence
@@ -277,7 +287,7 @@
-
+
Select executable - QSpectrumAnalyzer
diff --git a/qspectrumanalyzer/plot.py b/qspectrumanalyzer/plot.py
index 57adaf0..e9fbd55 100644
--- a/qspectrumanalyzer/plot.py
+++ b/qspectrumanalyzer/plot.py
@@ -1,10 +1,8 @@
import collections, math
-import numpy as np
+from PyQt4 import QtCore
import pyqtgraph as pg
-from qspectrumanalyzer.utils import smooth
-
# Basic PyQtGraph settings
pg.setConfigOptions(antialias=True)
@@ -23,19 +21,14 @@ class SpectrumPlotWidget:
self.persistence_length = 5
self.persistence_decay = "exponential"
self.persistence_color = pg.mkColor("g")
- self.peak_hold = False
- self.peak_hold_color = pg.mkColor("r")
- self.average = False
- self.average_color = pg.mkColor("c")
- self.smooth = False
- self.smooth_length = 11
- self.smooth_window = "hanning"
-
- self.counter = 0
- self.peak_hold_data = None
- self.average_data = None
self.persistence_data = None
self.persistence_curves = None
+ self.peak_hold_max = False
+ self.peak_hold_max_color = pg.mkColor("r")
+ self.peak_hold_min = False
+ self.peak_hold_min_color = pg.mkColor("b")
+ self.average = False
+ self.average_color = pg.mkColor("c")
self.create_plot()
@@ -51,8 +44,9 @@ class SpectrumPlotWidget:
self.create_persistence_curves()
self.create_average_curve()
+ self.create_peak_hold_min_curve()
+ self.create_peak_hold_max_curve()
self.create_main_curve()
- self.create_peak_hold_curve()
# Create crosshair
self.vLine = pg.InfiniteLine(angle=90, movable=False)
@@ -64,15 +58,20 @@ class SpectrumPlotWidget:
self.mouseProxy = pg.SignalProxy(self.plot.scene().sigMouseMoved,
rateLimit=60, slot=self.mouse_moved)
- def create_peak_hold_curve(self):
- """Create peak hold curve"""
- self.curve_peak_hold = self.plot.plot(pen=self.peak_hold_color)
- self.curve_peak_hold.setZValue(900)
-
def create_main_curve(self):
"""Create main spectrum curve"""
self.curve = self.plot.plot(pen=self.main_color)
- self.curve.setZValue(800)
+ self.curve.setZValue(900)
+
+ def create_peak_hold_max_curve(self):
+ """Create max. peak hold curve"""
+ self.curve_peak_hold_max = self.plot.plot(pen=self.peak_hold_max_color)
+ self.curve_peak_hold_max.setZValue(800)
+
+ def create_peak_hold_min_curve(self):
+ """Create min. peak hold curve"""
+ self.curve_peak_hold_min = self.plot.plot(pen=self.peak_hold_min_color)
+ self.curve_peak_hold_min.setZValue(800)
def create_average_curve(self):
"""Create average curve"""
@@ -94,7 +93,8 @@ class SpectrumPlotWidget:
def set_colors(self):
"""Set colors of all curves"""
self.curve.setPen(self.main_color)
- self.curve_peak_hold.setPen(self.peak_hold_color)
+ self.curve_peak_hold_max.setPen(self.peak_hold_max_color)
+ self.curve_peak_hold_min.setPen(self.peak_hold_min_color)
self.curve_average.setPen(self.average_color)
decay = self.get_decay()
@@ -118,47 +118,85 @@ class SpectrumPlotWidget:
else:
return self.decay_linear
- def update_plot(self, data):
- """Update main spectrum plot"""
- self.counter += 1
+ def update_plot(self, data_storage, force=False):
+ """Update main spectrum curve"""
+ if data_storage.x is None:
+ return
- # Apply smoothing to data
- if self.smooth:
- data["y"] = smooth(data["y"],
- window_len=self.smooth_length,
- window=self.smooth_window)
+ if self.main_curve or force:
+ self.curve.setData(data_storage.x, data_storage.y)
+ if force:
+ self.curve.setVisible(self.main_curve)
- # Draw main curve
- if self.main_curve:
- self.curve.setData(data["x"], data["y"])
+ def update_peak_hold_max(self, data_storage, force=False):
+ """Update max. peak hold curve"""
+ if data_storage.x is None:
+ return
- # Update peak hold data and draw peak hold curve
- if self.peak_hold:
- if self.peak_hold_data is None:
- self.peak_hold_data = data["y"].copy()
- else:
- for i, y in enumerate(data["y"]):
- if y > self.peak_hold_data[i]:
- self.peak_hold_data[i] = y
- self.curve_peak_hold.setData(data["x"], self.peak_hold_data)
+ if self.peak_hold_max or force:
+ self.curve_peak_hold_max.setData(data_storage.x, data_storage.peak_hold_max)
+ if force:
+ self.curve_peak_hold_max.setVisible(self.peak_hold_max)
- # Update average data and draw average curve
- if self.average:
- if self.average_data is None:
- self.average_data = data["y"].copy()
- else:
- for i, y in enumerate(data["y"]):
- self.average_data[i] = (self.counter * self.average_data[i] + y) / (self.counter + 1)
- self.curve_average.setData(data["x"], self.average_data)
+ def update_peak_hold_min(self, data_storage, force=False):
+ """Update min. peak hold curve"""
+ if data_storage.x is None:
+ return
- # Draw persistence curves
- if self.persistence:
+ if self.peak_hold_min or force:
+ self.curve_peak_hold_min.setData(data_storage.x, data_storage.peak_hold_min)
+ if force:
+ self.curve_peak_hold_min.setVisible(self.peak_hold_min)
+
+ def update_average(self, data_storage, force=False):
+ """Update average curve"""
+ if data_storage.x is None:
+ return
+
+ if self.average or force:
+ self.curve_average.setData(data_storage.x, data_storage.average)
+ if force:
+ self.curve_average.setVisible(self.average)
+
+ def update_persistence(self, data_storage, force=False):
+ """Update persistence curves"""
+ if data_storage.x is None:
+ return
+
+ if self.persistence or force:
if self.persistence_data is None:
self.persistence_data = collections.deque(maxlen=self.persistence_length)
else:
for i, y in enumerate(self.persistence_data):
- self.persistence_curves[i].setData(data["x"], y)
- self.persistence_data.appendleft(data["y"].copy())
+ curve = self.persistence_curves[i]
+ curve.setData(data_storage.x, y)
+ if force:
+ curve.setVisible(self.persistence)
+ self.persistence_data.appendleft(data_storage.y)
+
+ def recalculate_plot(self, data_storage):
+ """Recalculate plot from history"""
+ if data_storage.x is None:
+ return
+
+ QtCore.QTimer.singleShot(0, lambda: self.update_plot(data_storage, force=True))
+ QtCore.QTimer.singleShot(0, lambda: self.update_average(data_storage, force=True))
+ QtCore.QTimer.singleShot(0, lambda: self.update_peak_hold_max(data_storage, force=True))
+ QtCore.QTimer.singleShot(0, lambda: self.update_peak_hold_min(data_storage, force=True))
+
+ def recalculate_persistence(self, data_storage):
+ """Recalculate persistence data and update persistence curves"""
+ if data_storage.x is None:
+ return
+
+ self.clear_persistence()
+ self.persistence_data = collections.deque(maxlen=self.persistence_length)
+ for i in range(min(self.persistence_length, data_storage.history.history_size - 1)):
+ data = data_storage.history[-i - 2]
+ if data_storage.smooth:
+ data = data_storage.smooth_data(data)
+ self.persistence_data.append(data)
+ QtCore.QTimer.singleShot(0, lambda: self.update_persistence(data_storage, force=True))
def mouse_moved(self, evt):
"""Update crosshair when mouse is moved"""
@@ -166,34 +204,31 @@ class SpectrumPlotWidget:
if self.plot.sceneBoundingRect().contains(pos):
mousePoint = self.plot.vb.mapSceneToView(pos)
self.posLabel.setText(
- "f={:0.3f} MHz, P={:0.3f} dBm".format(mousePoint.x() / 1e6,
- mousePoint.y())
+ "f={:0.3f} MHz, P={:0.3f} dBm".format(
+ mousePoint.x() / 1e6,
+ mousePoint.y()
+ )
)
self.vLine.setPos(mousePoint.x())
self.hLine.setPos(mousePoint.y())
- def main_clear(self):
+ def clear_plot(self):
"""Clear main spectrum curve"""
self.curve.clear()
- self.plot.removeItem(self.curve)
- self.create_main_curve()
- def peak_hold_clear(self):
- """Clear peak hold curve"""
- self.peak_hold_data = None
- self.curve_peak_hold.clear()
- self.plot.removeItem(self.curve_peak_hold)
- self.create_peak_hold_curve()
+ def clear_peak_hold_max(self):
+ """Clear max. peak hold curve"""
+ self.curve_peak_hold_max.clear()
- def average_clear(self):
+ def clear_peak_hold_min(self):
+ """Clear min. peak hold curve"""
+ self.curve_peak_hold_min.clear()
+
+ def clear_average(self):
"""Clear average curve"""
- self.counter = 0
- self.average_data = None
self.curve_average.clear()
- self.plot.removeItem(self.curve_average)
- self.create_average_curve()
- def persistence_clear(self):
+ def clear_persistence(self):
"""Clear spectrum persistence curves"""
self.persistence_data = None
for curve in self.persistence_curves:
@@ -201,7 +236,6 @@ class SpectrumPlotWidget:
self.plot.removeItem(curve)
self.create_persistence_curves()
-
class WaterfallPlotWidget:
"""Waterfall plot"""
def __init__(self, layout, histogram_layout=None):
@@ -240,30 +274,32 @@ class WaterfallPlotWidget:
#self.histogram.setHistogramRange(-50, 0)
#self.histogram.setLevels(-50, 0)
- def update_plot(self, data):
+ def update_plot(self, data_storage):
"""Update waterfall plot"""
self.counter += 1
- # Create waterfall data array and waterfall image on first run
+ # Create waterfall image on first run
if self.counter == 1:
- self.waterfallImgArray = np.zeros((self.history_size, len(data["x"])))
self.waterfallImg = pg.ImageItem()
- self.waterfallImg.scale((data["x"][-1] - data["x"][0]) / len(data["x"]), 1)
+ self.waterfallImg.scale((data_storage.x[-1] - data_storage.x[0]) / len(data_storage.x), 1)
self.plot.clear()
self.plot.addItem(self.waterfallImg)
# Roll down one and replace leading edge with new data
- self.waterfallImgArray = np.roll(self.waterfallImgArray, -1, axis=0)
- self.waterfallImgArray[-1] = data["y"]
- self.waterfallImg.setImage(self.waterfallImgArray[-self.counter:].T,
+ self.waterfallImg.setImage(data_storage.history.buffer[-self.counter:].T,
autoLevels=False, autoRange=False)
# Move waterfall image to always start at 0
- self.waterfallImg.setPos(data["x"][0],
- -self.counter if self.counter < self.history_size
- else -self.history_size)
+ self.waterfallImg.setPos(
+ data_storage.x[0],
+ -self.counter if self.counter < self.history_size else -self.history_size
+ )
# Link histogram widget to waterfall image on first run
# (must be done after first data is received or else levels would be wrong)
if self.counter == 1 and self.histogram_layout:
self.histogram.setImageItem(self.waterfallImg)
+
+ def clear_plot(self):
+ """Clear waterfall plot"""
+ self.counter = 0
diff --git a/qspectrumanalyzer/qspectrumanalyzer.ui b/qspectrumanalyzer/qspectrumanalyzer.ui
index 0a410fc..63467ba 100644
--- a/qspectrumanalyzer/qspectrumanalyzer.ui
+++ b/qspectrumanalyzer/qspectrumanalyzer.ui
@@ -82,7 +82,7 @@
- 190
+ 10
10
@@ -143,7 +143,7 @@
- 225
+ 10
10
@@ -339,6 +339,25 @@
+ -
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ auto
+
+
+ -1
+
+
+ 49
+
+
+ -1
+
+
+
-
@@ -359,19 +378,6 @@
- -
-
-
- Qt::Vertical
-
-
-
- 20
- 1
-
-
-
-
-
@@ -395,10 +401,31 @@
- -
-
+
-
+
- Peak hold
+ Colors...
+
+
+
+ -
+
+
+ Max. hold
+
+
+
+ -
+
+
+ Min. hold
+
+
+
+ -
+
+
+ Average
@@ -409,46 +436,6 @@
- -
-
-
- Persistence
-
-
-
- -
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
- auto
-
-
- -1
-
-
- 49
-
-
- -1
-
-
-
- -
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
-
- -
-
-
- Colors...
-
-
-
-
@@ -459,6 +446,13 @@
+ -
+
+
+ Persistence
+
+
+
-
@@ -469,10 +463,23 @@
- -
-
-
- Average
+
-
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 1
+
+
+
+
+ -
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
@@ -549,7 +556,8 @@
cropSpinBox
mainCurveCheckBox
colorsButton
- peakHoldCheckBox
+ peakHoldMaxCheckBox
+ peakHoldMinCheckBox
averageCheckBox
smoothCheckBox
smoothButton
diff --git a/qspectrumanalyzer/qspectrumanalyzer_colors.ui b/qspectrumanalyzer/qspectrumanalyzer_colors.ui
index e2039cc..5802a86 100644
--- a/qspectrumanalyzer/qspectrumanalyzer_colors.ui
+++ b/qspectrumanalyzer/qspectrumanalyzer_colors.ui
@@ -6,8 +6,8 @@
0
0
- 202
- 214
+ 232
+ 260
@@ -42,15 +42,15 @@
-
- Peak hold color:
+ Max. peak hold color:
- peakHoldColorButton
+ peakHoldMaxColorButton
-
-
+
0
@@ -63,17 +63,17 @@
-
-
+
- Average color:
+ Min. peak hold color:
- averageColorButton
+ peakHoldMinColorButton
-
-
+
0
@@ -86,6 +86,29 @@
-
+
+
+ Average color:
+
+
+ averageColorButton
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ ...
+
+
+
+ -
Persistence color:
@@ -95,7 +118,7 @@
- -
+
-
@@ -144,7 +167,8 @@
mainColorButton
- peakHoldColorButton
+ peakHoldMaxColorButton
+ peakHoldMinColorButton
averageColorButton
persistenceColorButton
buttonBox
diff --git a/qspectrumanalyzer/ui_qspectrumanalyzer.py b/qspectrumanalyzer/ui_qspectrumanalyzer.py
index 27e5880..db0ed27 100644
--- a/qspectrumanalyzer/ui_qspectrumanalyzer.py
+++ b/qspectrumanalyzer/ui_qspectrumanalyzer.py
@@ -71,7 +71,7 @@ class Ui_QSpectrumAnalyzerMainWindow(object):
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.controlsDockWidget.sizePolicy().hasHeightForWidth())
self.controlsDockWidget.setSizePolicy(sizePolicy)
- self.controlsDockWidget.setMinimumSize(QtCore.QSize(190, 10))
+ self.controlsDockWidget.setMinimumSize(QtCore.QSize(10, 10))
self.controlsDockWidget.setFeatures(QtGui.QDockWidget.DockWidgetFloatable|QtGui.QDockWidget.DockWidgetMovable)
self.controlsDockWidget.setObjectName(_fromUtf8("controlsDockWidget"))
self.controlsDockWidgetContents = QtGui.QWidget()
@@ -97,7 +97,7 @@ class Ui_QSpectrumAnalyzerMainWindow(object):
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.frequencyDockWidget.sizePolicy().hasHeightForWidth())
self.frequencyDockWidget.setSizePolicy(sizePolicy)
- self.frequencyDockWidget.setMinimumSize(QtCore.QSize(225, 10))
+ self.frequencyDockWidget.setMinimumSize(QtCore.QSize(10, 10))
self.frequencyDockWidget.setFeatures(QtGui.QDockWidget.DockWidgetFloatable|QtGui.QDockWidget.DockWidgetMovable)
self.frequencyDockWidget.setObjectName(_fromUtf8("frequencyDockWidget"))
self.frequencyDockWidgetContents = QtGui.QWidget()
@@ -180,14 +180,19 @@ class Ui_QSpectrumAnalyzerMainWindow(object):
self.intervalSpinBox.setProperty("value", 1.0)
self.intervalSpinBox.setObjectName(_fromUtf8("intervalSpinBox"))
self.gridLayout.addWidget(self.intervalSpinBox, 1, 0, 1, 1)
+ self.gainSpinBox = QtGui.QSpinBox(self.settingsDockWidgetContents)
+ self.gainSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.gainSpinBox.setMinimum(-1)
+ self.gainSpinBox.setMaximum(49)
+ self.gainSpinBox.setProperty("value", -1)
+ self.gainSpinBox.setObjectName(_fromUtf8("gainSpinBox"))
+ self.gridLayout.addWidget(self.gainSpinBox, 1, 1, 1, 2)
self.label_5 = QtGui.QLabel(self.settingsDockWidgetContents)
self.label_5.setObjectName(_fromUtf8("label_5"))
self.gridLayout.addWidget(self.label_5, 2, 0, 1, 1)
self.label_7 = QtGui.QLabel(self.settingsDockWidgetContents)
self.label_7.setObjectName(_fromUtf8("label_7"))
self.gridLayout.addWidget(self.label_7, 2, 1, 1, 1)
- spacerItem2 = QtGui.QSpacerItem(20, 1, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
- self.gridLayout.addItem(spacerItem2, 9, 0, 1, 1)
self.ppmSpinBox = QtGui.QSpinBox(self.settingsDockWidgetContents)
self.ppmSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.ppmSpinBox.setMinimum(-999)
@@ -198,40 +203,38 @@ class Ui_QSpectrumAnalyzerMainWindow(object):
self.mainCurveCheckBox.setChecked(True)
self.mainCurveCheckBox.setObjectName(_fromUtf8("mainCurveCheckBox"))
self.gridLayout.addWidget(self.mainCurveCheckBox, 4, 0, 1, 1)
- self.peakHoldCheckBox = QtGui.QCheckBox(self.settingsDockWidgetContents)
- self.peakHoldCheckBox.setObjectName(_fromUtf8("peakHoldCheckBox"))
- self.gridLayout.addWidget(self.peakHoldCheckBox, 5, 0, 1, 1)
- self.smoothCheckBox = QtGui.QCheckBox(self.settingsDockWidgetContents)
- self.smoothCheckBox.setObjectName(_fromUtf8("smoothCheckBox"))
- self.gridLayout.addWidget(self.smoothCheckBox, 7, 0, 1, 1)
- self.persistenceCheckBox = QtGui.QCheckBox(self.settingsDockWidgetContents)
- self.persistenceCheckBox.setObjectName(_fromUtf8("persistenceCheckBox"))
- self.gridLayout.addWidget(self.persistenceCheckBox, 8, 0, 1, 1)
- self.gainSpinBox = QtGui.QSpinBox(self.settingsDockWidgetContents)
- self.gainSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
- self.gainSpinBox.setMinimum(-1)
- self.gainSpinBox.setMaximum(49)
- self.gainSpinBox.setProperty("value", -1)
- self.gainSpinBox.setObjectName(_fromUtf8("gainSpinBox"))
- self.gridLayout.addWidget(self.gainSpinBox, 1, 1, 1, 2)
- self.cropSpinBox = QtGui.QSpinBox(self.settingsDockWidgetContents)
- self.cropSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
- self.cropSpinBox.setObjectName(_fromUtf8("cropSpinBox"))
- self.gridLayout.addWidget(self.cropSpinBox, 3, 1, 1, 2)
self.colorsButton = QtGui.QPushButton(self.settingsDockWidgetContents)
self.colorsButton.setObjectName(_fromUtf8("colorsButton"))
self.gridLayout.addWidget(self.colorsButton, 4, 1, 1, 2)
+ self.peakHoldMaxCheckBox = QtGui.QCheckBox(self.settingsDockWidgetContents)
+ self.peakHoldMaxCheckBox.setObjectName(_fromUtf8("peakHoldMaxCheckBox"))
+ self.gridLayout.addWidget(self.peakHoldMaxCheckBox, 5, 0, 1, 1)
+ self.peakHoldMinCheckBox = QtGui.QCheckBox(self.settingsDockWidgetContents)
+ self.peakHoldMinCheckBox.setObjectName(_fromUtf8("peakHoldMinCheckBox"))
+ self.gridLayout.addWidget(self.peakHoldMinCheckBox, 5, 1, 1, 2)
+ self.averageCheckBox = QtGui.QCheckBox(self.settingsDockWidgetContents)
+ self.averageCheckBox.setObjectName(_fromUtf8("averageCheckBox"))
+ self.gridLayout.addWidget(self.averageCheckBox, 6, 0, 1, 1)
+ self.smoothCheckBox = QtGui.QCheckBox(self.settingsDockWidgetContents)
+ self.smoothCheckBox.setObjectName(_fromUtf8("smoothCheckBox"))
+ self.gridLayout.addWidget(self.smoothCheckBox, 7, 0, 1, 1)
self.smoothButton = QtGui.QToolButton(self.settingsDockWidgetContents)
self.smoothButton.setAutoRaise(False)
self.smoothButton.setObjectName(_fromUtf8("smoothButton"))
self.gridLayout.addWidget(self.smoothButton, 7, 2, 1, 1)
+ self.persistenceCheckBox = QtGui.QCheckBox(self.settingsDockWidgetContents)
+ self.persistenceCheckBox.setObjectName(_fromUtf8("persistenceCheckBox"))
+ self.gridLayout.addWidget(self.persistenceCheckBox, 8, 0, 1, 1)
self.persistenceButton = QtGui.QToolButton(self.settingsDockWidgetContents)
self.persistenceButton.setAutoRaise(False)
self.persistenceButton.setObjectName(_fromUtf8("persistenceButton"))
self.gridLayout.addWidget(self.persistenceButton, 8, 2, 1, 1)
- self.averageCheckBox = QtGui.QCheckBox(self.settingsDockWidgetContents)
- self.averageCheckBox.setObjectName(_fromUtf8("averageCheckBox"))
- self.gridLayout.addWidget(self.averageCheckBox, 6, 0, 1, 1)
+ spacerItem2 = QtGui.QSpacerItem(20, 1, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
+ self.gridLayout.addItem(spacerItem2, 9, 0, 1, 1)
+ self.cropSpinBox = QtGui.QSpinBox(self.settingsDockWidgetContents)
+ self.cropSpinBox.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
+ self.cropSpinBox.setObjectName(_fromUtf8("cropSpinBox"))
+ self.gridLayout.addWidget(self.cropSpinBox, 3, 1, 1, 2)
self.settingsDockWidget.setWidget(self.settingsDockWidgetContents)
QSpectrumAnalyzerMainWindow.addDockWidget(QtCore.Qt.DockWidgetArea(2), self.settingsDockWidget)
self.levelsDockWidget = QtGui.QDockWidget(QSpectrumAnalyzerMainWindow)
@@ -289,8 +292,9 @@ class Ui_QSpectrumAnalyzerMainWindow(object):
QSpectrumAnalyzerMainWindow.setTabOrder(self.ppmSpinBox, self.cropSpinBox)
QSpectrumAnalyzerMainWindow.setTabOrder(self.cropSpinBox, self.mainCurveCheckBox)
QSpectrumAnalyzerMainWindow.setTabOrder(self.mainCurveCheckBox, self.colorsButton)
- QSpectrumAnalyzerMainWindow.setTabOrder(self.colorsButton, self.peakHoldCheckBox)
- QSpectrumAnalyzerMainWindow.setTabOrder(self.peakHoldCheckBox, self.averageCheckBox)
+ QSpectrumAnalyzerMainWindow.setTabOrder(self.colorsButton, self.peakHoldMaxCheckBox)
+ QSpectrumAnalyzerMainWindow.setTabOrder(self.peakHoldMaxCheckBox, self.peakHoldMinCheckBox)
+ QSpectrumAnalyzerMainWindow.setTabOrder(self.peakHoldMinCheckBox, self.averageCheckBox)
QSpectrumAnalyzerMainWindow.setTabOrder(self.averageCheckBox, self.smoothCheckBox)
QSpectrumAnalyzerMainWindow.setTabOrder(self.smoothCheckBox, self.smoothButton)
QSpectrumAnalyzerMainWindow.setTabOrder(self.smoothButton, self.persistenceCheckBox)
@@ -317,17 +321,18 @@ class Ui_QSpectrumAnalyzerMainWindow(object):
self.settingsDockWidget.setWindowTitle(_translate("QSpectrumAnalyzerMainWindow", "Settings", None))
self.label_4.setText(_translate("QSpectrumAnalyzerMainWindow", "Interval [s]:", None))
self.label_6.setText(_translate("QSpectrumAnalyzerMainWindow", "Gain [dB]:", None))
+ self.gainSpinBox.setSpecialValueText(_translate("QSpectrumAnalyzerMainWindow", "auto", None))
self.label_5.setText(_translate("QSpectrumAnalyzerMainWindow", "Corr. [ppm]:", None))
self.label_7.setText(_translate("QSpectrumAnalyzerMainWindow", "Crop [%]:", None))
self.mainCurveCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Main curve", None))
- self.peakHoldCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Peak hold", None))
- self.smoothCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Smoothing", None))
- self.persistenceCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Persistence", None))
- self.gainSpinBox.setSpecialValueText(_translate("QSpectrumAnalyzerMainWindow", "auto", None))
self.colorsButton.setText(_translate("QSpectrumAnalyzerMainWindow", "Colors...", None))
- self.smoothButton.setText(_translate("QSpectrumAnalyzerMainWindow", "...", None))
- self.persistenceButton.setText(_translate("QSpectrumAnalyzerMainWindow", "...", None))
+ self.peakHoldMaxCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Max. hold", None))
+ self.peakHoldMinCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Min. hold", None))
self.averageCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Average", None))
+ self.smoothCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Smoothing", None))
+ self.smoothButton.setText(_translate("QSpectrumAnalyzerMainWindow", "...", None))
+ self.persistenceCheckBox.setText(_translate("QSpectrumAnalyzerMainWindow", "Persistence", None))
+ self.persistenceButton.setText(_translate("QSpectrumAnalyzerMainWindow", "...", None))
self.levelsDockWidget.setWindowTitle(_translate("QSpectrumAnalyzerMainWindow", "Levels", None))
self.action_Settings.setText(_translate("QSpectrumAnalyzerMainWindow", "&Settings...", None))
self.action_Quit.setText(_translate("QSpectrumAnalyzerMainWindow", "&Quit", None))
diff --git a/qspectrumanalyzer/ui_qspectrumanalyzer_colors.py b/qspectrumanalyzer/ui_qspectrumanalyzer_colors.py
index f4aa3f4..4c691e1 100644
--- a/qspectrumanalyzer/ui_qspectrumanalyzer_colors.py
+++ b/qspectrumanalyzer/ui_qspectrumanalyzer_colors.py
@@ -25,7 +25,7 @@ except AttributeError:
class Ui_QSpectrumAnalyzerColors(object):
def setupUi(self, QSpectrumAnalyzerColors):
QSpectrumAnalyzerColors.setObjectName(_fromUtf8("QSpectrumAnalyzerColors"))
- QSpectrumAnalyzerColors.resize(202, 214)
+ QSpectrumAnalyzerColors.resize(232, 260)
self.verticalLayout = QtGui.QVBoxLayout(QSpectrumAnalyzerColors)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.formLayout = QtGui.QFormLayout()
@@ -44,17 +44,28 @@ class Ui_QSpectrumAnalyzerColors(object):
self.label_4 = QtGui.QLabel(QSpectrumAnalyzerColors)
self.label_4.setObjectName(_fromUtf8("label_4"))
self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.label_4)
- self.peakHoldColorButton = ColorButton(QSpectrumAnalyzerColors)
+ self.peakHoldMaxColorButton = ColorButton(QSpectrumAnalyzerColors)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
- sizePolicy.setHeightForWidth(self.peakHoldColorButton.sizePolicy().hasHeightForWidth())
- self.peakHoldColorButton.setSizePolicy(sizePolicy)
- self.peakHoldColorButton.setObjectName(_fromUtf8("peakHoldColorButton"))
- self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, self.peakHoldColorButton)
+ sizePolicy.setHeightForWidth(self.peakHoldMaxColorButton.sizePolicy().hasHeightForWidth())
+ self.peakHoldMaxColorButton.setSizePolicy(sizePolicy)
+ self.peakHoldMaxColorButton.setObjectName(_fromUtf8("peakHoldMaxColorButton"))
+ self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, self.peakHoldMaxColorButton)
+ self.label_6 = QtGui.QLabel(QSpectrumAnalyzerColors)
+ self.label_6.setObjectName(_fromUtf8("label_6"))
+ self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.label_6)
+ self.peakHoldMinColorButton = ColorButton(QSpectrumAnalyzerColors)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.peakHoldMinColorButton.sizePolicy().hasHeightForWidth())
+ self.peakHoldMinColorButton.setSizePolicy(sizePolicy)
+ self.peakHoldMinColorButton.setObjectName(_fromUtf8("peakHoldMinColorButton"))
+ self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.peakHoldMinColorButton)
self.label_5 = QtGui.QLabel(QSpectrumAnalyzerColors)
self.label_5.setObjectName(_fromUtf8("label_5"))
- self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.label_5)
+ self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, self.label_5)
self.averageColorButton = ColorButton(QSpectrumAnalyzerColors)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -62,10 +73,10 @@ class Ui_QSpectrumAnalyzerColors(object):
sizePolicy.setHeightForWidth(self.averageColorButton.sizePolicy().hasHeightForWidth())
self.averageColorButton.setSizePolicy(sizePolicy)
self.averageColorButton.setObjectName(_fromUtf8("averageColorButton"))
- self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.averageColorButton)
+ self.formLayout.setWidget(3, QtGui.QFormLayout.FieldRole, self.averageColorButton)
self.label_3 = QtGui.QLabel(QSpectrumAnalyzerColors)
self.label_3.setObjectName(_fromUtf8("label_3"))
- self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, self.label_3)
+ self.formLayout.setWidget(4, QtGui.QFormLayout.LabelRole, self.label_3)
self.persistenceColorButton = ColorButton(QSpectrumAnalyzerColors)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -73,7 +84,7 @@ class Ui_QSpectrumAnalyzerColors(object):
sizePolicy.setHeightForWidth(self.persistenceColorButton.sizePolicy().hasHeightForWidth())
self.persistenceColorButton.setSizePolicy(sizePolicy)
self.persistenceColorButton.setObjectName(_fromUtf8("persistenceColorButton"))
- self.formLayout.setWidget(3, QtGui.QFormLayout.FieldRole, self.persistenceColorButton)
+ self.formLayout.setWidget(4, QtGui.QFormLayout.FieldRole, self.persistenceColorButton)
self.verticalLayout.addLayout(self.formLayout)
spacerItem = QtGui.QSpacerItem(20, 2, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem)
@@ -83,7 +94,8 @@ class Ui_QSpectrumAnalyzerColors(object):
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.verticalLayout.addWidget(self.buttonBox)
self.label_2.setBuddy(self.mainColorButton)
- self.label_4.setBuddy(self.peakHoldColorButton)
+ self.label_4.setBuddy(self.peakHoldMaxColorButton)
+ self.label_6.setBuddy(self.peakHoldMinColorButton)
self.label_5.setBuddy(self.averageColorButton)
self.label_3.setBuddy(self.persistenceColorButton)
@@ -91,8 +103,9 @@ class Ui_QSpectrumAnalyzerColors(object):
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), QSpectrumAnalyzerColors.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), QSpectrumAnalyzerColors.reject)
QtCore.QMetaObject.connectSlotsByName(QSpectrumAnalyzerColors)
- QSpectrumAnalyzerColors.setTabOrder(self.mainColorButton, self.peakHoldColorButton)
- QSpectrumAnalyzerColors.setTabOrder(self.peakHoldColorButton, self.averageColorButton)
+ QSpectrumAnalyzerColors.setTabOrder(self.mainColorButton, self.peakHoldMaxColorButton)
+ QSpectrumAnalyzerColors.setTabOrder(self.peakHoldMaxColorButton, self.peakHoldMinColorButton)
+ QSpectrumAnalyzerColors.setTabOrder(self.peakHoldMinColorButton, self.averageColorButton)
QSpectrumAnalyzerColors.setTabOrder(self.averageColorButton, self.persistenceColorButton)
QSpectrumAnalyzerColors.setTabOrder(self.persistenceColorButton, self.buttonBox)
@@ -100,8 +113,10 @@ class Ui_QSpectrumAnalyzerColors(object):
QSpectrumAnalyzerColors.setWindowTitle(_translate("QSpectrumAnalyzerColors", "Colors - QSpectrumAnalyzer", None))
self.label_2.setText(_translate("QSpectrumAnalyzerColors", "Main curve color:", None))
self.mainColorButton.setText(_translate("QSpectrumAnalyzerColors", "...", None))
- self.label_4.setText(_translate("QSpectrumAnalyzerColors", "Peak hold color:", None))
- self.peakHoldColorButton.setText(_translate("QSpectrumAnalyzerColors", "...", None))
+ self.label_4.setText(_translate("QSpectrumAnalyzerColors", "Max. peak hold color:", None))
+ self.peakHoldMaxColorButton.setText(_translate("QSpectrumAnalyzerColors", "...", None))
+ self.label_6.setText(_translate("QSpectrumAnalyzerColors", "Min. peak hold color:", None))
+ self.peakHoldMinColorButton.setText(_translate("QSpectrumAnalyzerColors", "...", None))
self.label_5.setText(_translate("QSpectrumAnalyzerColors", "Average color:", None))
self.averageColorButton.setText(_translate("QSpectrumAnalyzerColors", "...", None))
self.label_3.setText(_translate("QSpectrumAnalyzerColors", "Persistence color:", None))