Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some Analysis #357

Merged
merged 12 commits into from
Dec 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 99 additions & 3 deletions NanoVNASaver/Analysis/Analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,109 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
import math

import numpy as np
from scipy.signal import argrelextrema
from PyQt5 import QtWidgets
from scipy import signal

logger = logging.getLogger(__name__)


class Analysis:
_widget = None

@classmethod
def find_crossing_zero(cls, data, threshold=0):
'''

Find values crossing zero
return list of tuples (before, crossing, after)
indicating the index of data list
crossing is where data == 0
or data nearest 0

at maximum 1 value == 0
data must not start or end with 0


:param cls:
:param data: list of values
:param threshold: unused, for future manage flipping around 0
'''
my_data = np.array(data)
zeroes = np.where(my_data == 0)[0]

if 0 in zeroes:
raise ValueError("Data must non start with 0")

if len(data) - 1 in zeroes:
raise ValueError("Data must non end with 0")
crossing = [(n - 1, n, n + 1) for n in zeroes]

for n in np.where((my_data[:-1] * my_data[1:]) < 0)[0]:
if abs(data[n]) <= abs(data[n + 1]):
crossing.append((n, n, n + 1))
else:
crossing.append((n, n + 1, n + 1))

return crossing

@classmethod
def find_minimums(cls, data, threshold):
'''

Find values above threshold
return list of tuples (start, lowest, end)
indicating the index of data list


:param cls:
:param data: list of values
:param threshold:
'''

minimums = []
min_start = -1
min_idx = -1

min_val = threshold
for i, d in enumerate(data):
if d < threshold and i < len(data) - 1:
if d < min_val:
min_val = d
min_idx = i
if min_start == -1:
min_start = i
elif min_start != -1:
# We are above the threshold, and were in a section that was
# below
minimums.append((min_start, min_idx, i - 1))
min_start = -1
min_idx = -1
min_val = threshold
return minimums

@classmethod
def find_maximums(cls, data, threshold=None):
'''

Find peacs


:param cls:
:param data: list of values
:param threshold:
'''
peaks, _ = signal.find_peaks(
data, width=2, distance=3, prominence=1)

# my_data = np.array(data)
# maximums = argrelextrema(my_data, np.greater)[0]
if threshold is None:
return peaks
else:
return [k for k in peaks if data[k] > threshold]

def __init__(self, app: QtWidgets.QWidget):
self.app = app

Expand All @@ -50,8 +144,10 @@ def calculateRolloff(self, location1, location2):
if frequency_factor < 1:
frequency_factor = 1 / frequency_factor
attenuation = abs(gain1 - gain2)
logger.debug("Measured points: %d Hz and %d Hz", frequency1, frequency2)
logger.debug("Measured points: %d Hz and %d Hz",
frequency1, frequency2)
logger.debug("%f dB over %f factor", attenuation, frequency_factor)
octave_attenuation = attenuation / (math.log10(frequency_factor) / math.log10(2))
octave_attenuation = attenuation / \
(math.log10(frequency_factor) / math.log10(2))
decade_attenuation = attenuation / math.log10(frequency_factor)
return octave_attenuation, decade_attenuation
82 changes: 56 additions & 26 deletions NanoVNASaver/Analysis/PeakSearchAnalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
import numpy as np

from NanoVNASaver.Analysis import Analysis
from NanoVNASaver.Formatting import format_vswr
from NanoVNASaver.Formatting import format_gain
from NanoVNASaver.Formatting import format_resistance
from NanoVNASaver.Formatting import format_frequency_short


logger = logging.getLogger(__name__)
Expand All @@ -38,8 +42,8 @@ def __init__(self, app):
super().__init__(app)

self._widget = QtWidgets.QWidget()
outer_layout = QtWidgets.QFormLayout()
self._widget.setLayout(outer_layout)
self.layout = QtWidgets.QFormLayout()
self._widget.setLayout(self.layout)

self.rbtn_data_group = QtWidgets.QButtonGroup()
self.rbtn_data_vswr = QtWidgets.QRadioButton("VSWR")
Expand Down Expand Up @@ -70,40 +74,56 @@ def __init__(self, app):

self.checkbox_move_markers = QtWidgets.QCheckBox()

outer_layout.addRow(QtWidgets.QLabel("<b>Settings</b>"))
outer_layout.addRow("Data source", self.rbtn_data_vswr)
outer_layout.addRow("", self.rbtn_data_resistance)
outer_layout.addRow("", self.rbtn_data_reactance)
outer_layout.addRow("", self.rbtn_data_s21_gain)
outer_layout.addRow(PeakSearchAnalysis.QHLine())
outer_layout.addRow("Peak type", self.rbtn_peak_positive)
outer_layout.addRow("", self.rbtn_peak_negative)
self.layout.addRow(QtWidgets.QLabel("<b>Settings</b>"))
self.layout.addRow("Data source", self.rbtn_data_vswr)
self.layout.addRow("", self.rbtn_data_resistance)
self.layout.addRow("", self.rbtn_data_reactance)
self.layout.addRow("", self.rbtn_data_s21_gain)
self.layout.addRow(PeakSearchAnalysis.QHLine())
self.layout.addRow("Peak type", self.rbtn_peak_positive)
self.layout.addRow("", self.rbtn_peak_negative)
# outer_layout.addRow("", self.rbtn_peak_both)
outer_layout.addRow(PeakSearchAnalysis.QHLine())
outer_layout.addRow("Max number of peaks", self.input_number_of_peaks)
outer_layout.addRow("Move markers", self.checkbox_move_markers)
outer_layout.addRow(PeakSearchAnalysis.QHLine())

outer_layout.addRow(QtWidgets.QLabel("<b>Results</b>"))
self.layout.addRow(PeakSearchAnalysis.QHLine())
self.layout.addRow("Max number of peaks", self.input_number_of_peaks)
self.layout.addRow("Move markers", self.checkbox_move_markers)
self.layout.addRow(PeakSearchAnalysis.QHLine())
self.layout.addRow(QtWidgets.QLabel("<b>Results</b>"))
self.results_header = self.layout.rowCount()

def runAnalysis(self):
self.reset()
data = []
sign = 1
count = self.input_number_of_peaks.value()
if self.rbtn_data_vswr.isChecked():
data = []
fn = format_vswr
for d in self.app.data11:
data11.append(d.vswr)
data.append(d.vswr)
elif self.rbtn_data_s21_gain.isChecked():
data = []
fn = format_gain
for d in self.app.data21:
data.append(d.gain)
elif self.rbtn_data_resistance.isChecked():
fn = format_resistance
for d in self.app.data11:
data.append(d.impedance().real)
elif self.rbtn_data_reactance.isChecked():
fn = str
for d in self.app.data11:
data.append(d.impedance().imag)

else:
logger.warning("Searching for peaks on unknown data")
return

if self.rbtn_peak_positive.isChecked():
peaks, _ = signal.find_peaks(data, width=3, distance=3, prominence=1)
peaks, _ = signal.find_peaks(
data, width=3, distance=3, prominence=1)
elif self.rbtn_peak_negative.isChecked():
peaks, _ = signal.find_peaks(np.array(data)*-1, width=3, distance=3, prominence=1)
sign = -1
data = [x * sign for x in data]
peaks, _ = signal.find_peaks(
data, width=3, distance=3, prominence=1)
# elif self.rbtn_peak_both.isChecked():
# peaks_max, _ = signal.find_peaks(data, width=3, distance=3, prominence=1)
# peaks_min, _ = signal.find_peaks(np.array(data)*-1, width=3, distance=3, prominence=1)
Expand All @@ -117,8 +137,8 @@ def runAnalysis(self):

# Having found the peaks, get the prominence data

for p in peaks:
logger.debug("Peak at %d", p)
for i, p in np.ndenumerate(peaks):
logger.debug("Peak %i at %d", i, p)
prominences = signal.peak_prominences(data, peaks)[0]
logger.debug("%d prominences", len(prominences))

Expand All @@ -131,9 +151,13 @@ def runAnalysis(self):
logger.debug("Prominence %f", prominences[i])
logger.debug("Index in sweep %d", peaks[i])
logger.debug("Frequency %d", self.app.data11[peaks[i]].freq)
logger.debug("Value %f", data[peaks[i]])
logger.debug("Value %f", sign * data[peaks[i]])
self.layout.addRow(
f"Freq {format_frequency_short(self.app.data11[peaks[i]].freq)}",
QtWidgets.QLabel(f" value {fn(sign * data[peaks[i]])}"
))

if self.checkbox_move_markers:
if self.checkbox_move_markers.isChecked():
if count > len(self.app.markers):
logger.warning("More peaks found than there are markers")
for i in range(min(count, len(self.app.markers))):
Expand All @@ -152,4 +176,10 @@ def runAnalysis(self):
logger.debug("Max peak at %d, value %f", max_idx, max_val)

def reset(self):
pass
logger.debug("Reset analysis")

logger.debug("Results start at %d, out of %d",
self.results_header, self.layout.rowCount())
for i in range(self.results_header, self.layout.rowCount()):
logger.debug("deleting %s", self.layout.rowCount())
self.layout.removeRow(self.layout.rowCount() - 1)
Loading