kopia lustrzana https://github.com/NanoVNA-Saver/nanovna-saver
Merge pull request #357 from gaionim/features/crossingZero
Additinal analysis methods added / better peak search algorythmpull/368/head
commit
90e4dc9332
|
@ -18,8 +18,10 @@
|
|||
# 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__)
|
||||
|
||||
|
@ -27,6 +29,98 @@ 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
|
||||
|
||||
|
@ -50,8 +144,10 @@ class Analysis:
|
|||
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
|
||||
|
|
|
@ -23,6 +23,10 @@ from scipy import signal
|
|||
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__)
|
||||
|
@ -38,8 +42,8 @@ class PeakSearchAnalysis(Analysis):
|
|||
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")
|
||||
|
@ -70,40 +74,56 @@ class PeakSearchAnalysis(Analysis):
|
|||
|
||||
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)
|
||||
|
@ -117,8 +137,8 @@ class PeakSearchAnalysis(Analysis):
|
|||
|
||||
# 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))
|
||||
|
||||
|
@ -131,9 +151,13 @@ class PeakSearchAnalysis(Analysis):
|
|||
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))):
|
||||
|
@ -152,4 +176,10 @@ class PeakSearchAnalysis(Analysis):
|
|||
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)
|
||||
|
|
|
@ -23,14 +23,32 @@ import numpy as np
|
|||
|
||||
from NanoVNASaver.Analysis import Analysis, PeakSearchAnalysis
|
||||
from NanoVNASaver.Formatting import format_frequency
|
||||
from NanoVNASaver.Formatting import format_complex_imp
|
||||
from NanoVNASaver.RFTools import reflection_coefficient
|
||||
import os
|
||||
import csv
|
||||
from NanoVNASaver.Marker.Values import Label
|
||||
from NanoVNASaver.Marker.Widget import MarkerLabel
|
||||
from NanoVNASaver.Marker.Widget import Marker
|
||||
from collections import OrderedDict
|
||||
from NanoVNASaver.Formatting import format_frequency_short
|
||||
from NanoVNASaver.Formatting import format_resistance
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def round_2(x):
|
||||
return round(x, 2)
|
||||
|
||||
|
||||
def format_resistence_neg(x):
|
||||
return format_resistance(x, allow_negative=True)
|
||||
|
||||
|
||||
class VSWRAnalysis(Analysis):
|
||||
max_dips_shown = 3
|
||||
vswr_limit_value = 1.5
|
||||
|
||||
|
||||
class QHLine(QtWidgets.QFrame):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
@ -61,6 +79,7 @@ class VSWRAnalysis(Analysis):
|
|||
def runAnalysis(self):
|
||||
max_dips_shown = self.max_dips_shown
|
||||
data = []
|
||||
|
||||
for d in self.app.data11:
|
||||
data.append(d.vswr)
|
||||
# min_idx = np.argmin(data)
|
||||
|
@ -73,31 +92,17 @@ class VSWRAnalysis(Analysis):
|
|||
# self.app.markers[0].setFrequency(str(self.app.data11[min_idx].freq))
|
||||
# self.app.markers[0].frequencyInput.setText(str(self.app.data11[min_idx].freq))
|
||||
|
||||
minimums = []
|
||||
min_start = -1
|
||||
min_idx = -1
|
||||
threshold = self.input_vswr_limit.value()
|
||||
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
|
||||
minimums = self.find_minimums(data, threshold)
|
||||
|
||||
logger.debug("Found %d sections under %f threshold", len(minimums), threshold)
|
||||
logger.debug("Found %d sections under %f threshold",
|
||||
len(minimums), threshold)
|
||||
|
||||
results_header = self.layout.indexOf(self.results_label)
|
||||
logger.debug("Results start at %d, out of %d", results_header, self.layout.rowCount())
|
||||
logger.debug("Results start at %d, out of %d",
|
||||
results_header, self.layout.rowCount())
|
||||
for i in range(results_header, self.layout.rowCount()):
|
||||
self.layout.removeRow(self.layout.rowCount()-1)
|
||||
self.layout.removeRow(self.layout.rowCount() - 1)
|
||||
|
||||
if len(minimums) > max_dips_shown:
|
||||
self.layout.addRow(QtWidgets.QLabel("<b>More than " + str(max_dips_shown) +
|
||||
|
@ -141,7 +146,341 @@ class VSWRAnalysis(Analysis):
|
|||
format_frequency(self.app.data11[lowest].freq)))
|
||||
self.layout.addWidget(PeakSearchAnalysis.QHLine())
|
||||
# Remove the final separator line
|
||||
self.layout.removeRow(self.layout.rowCount()-1)
|
||||
self.layout.removeRow(self.layout.rowCount() - 1)
|
||||
else:
|
||||
self.layout.addRow(QtWidgets.QLabel(
|
||||
"No areas found with VSWR below " + str(round(threshold, 2)) + "."))
|
||||
|
||||
|
||||
class ResonanceAnalysis(Analysis):
|
||||
# max_dips_shown = 3
|
||||
|
||||
@classmethod
|
||||
def vswr_transformed(cls, z, ratio=49) -> float:
|
||||
refl = reflection_coefficient(z / ratio)
|
||||
mag = abs(refl)
|
||||
if mag == 1:
|
||||
return 1
|
||||
return (1 + mag) / (1 - mag)
|
||||
|
||||
class QHLine(QtWidgets.QFrame):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
|
||||
self._widget = QtWidgets.QWidget()
|
||||
self.layout = QtWidgets.QFormLayout()
|
||||
self._widget.setLayout(self.layout)
|
||||
self.input_description = QtWidgets.QLineEdit("")
|
||||
self.checkbox_move_marker = QtWidgets.QCheckBox()
|
||||
self.layout.addRow(QtWidgets.QLabel("<b>Settings</b>"))
|
||||
self.layout.addRow("Description", self.input_description)
|
||||
self.layout.addRow(VSWRAnalysis.QHLine())
|
||||
|
||||
self.layout.addRow(VSWRAnalysis.QHLine())
|
||||
|
||||
self.results_label = QtWidgets.QLabel("<b>Results</b>")
|
||||
self.layout.addRow(self.results_label)
|
||||
|
||||
def _get_data(self, index):
|
||||
my_data = {"freq": self.app.data11[index].freq,
|
||||
"s11": self.app.data11[index].z,
|
||||
"lambda": self.app.data11[index].wavelength,
|
||||
"impedance": self.app.data11[index].impedance(),
|
||||
"vswr": self.app.data11[index].vswr,
|
||||
}
|
||||
my_data["vswr_49"] = self.vswr_transformed(
|
||||
my_data["impedance"], 49)
|
||||
my_data["vswr_4"] = self.vswr_transformed(
|
||||
my_data["impedance"], 4)
|
||||
my_data["r"] = my_data["impedance"].real
|
||||
my_data["x"] = my_data["impedance"].imag
|
||||
|
||||
return my_data
|
||||
|
||||
def _get_crossing(self):
|
||||
|
||||
data = []
|
||||
for d in self.app.data11:
|
||||
data.append(d.phase)
|
||||
|
||||
crossing = sorted(self.find_crossing_zero(data))
|
||||
return crossing
|
||||
|
||||
def runAnalysis(self):
|
||||
self.reset()
|
||||
# self.results_label = QtWidgets.QLabel("<b>Results</b>")
|
||||
# max_dips_shown = self.max_dips_shown
|
||||
description = self.input_description.text()
|
||||
if description:
|
||||
filename = os.path.join("/tmp/", "{}.csv".format(description))
|
||||
else:
|
||||
filename = None
|
||||
|
||||
crossing = self._get_crossing()
|
||||
|
||||
logger.debug("Found %d sections ",
|
||||
len(crossing))
|
||||
|
||||
results_header = self.layout.indexOf(self.results_label)
|
||||
logger.debug("Results start at %d, out of %d",
|
||||
results_header, self.layout.rowCount())
|
||||
for i in range(results_header, self.layout.rowCount()):
|
||||
self.layout.removeRow(self.layout.rowCount() - 1)
|
||||
|
||||
# if len(crossing) > max_dips_shown:
|
||||
# self.layout.addRow(QtWidgets.QLabel("<b>More than " + str(max_dips_shown) +
|
||||
# " dips found. Lowest shown.</b>"))
|
||||
|
||||
# self.crossing = crossing[:max_dips_shown]
|
||||
extended_data = []
|
||||
if len(crossing) > 0:
|
||||
|
||||
for m in crossing:
|
||||
start, lowest, end = m
|
||||
my_data = self._get_data(lowest)
|
||||
|
||||
extended_data.append(my_data)
|
||||
if start != end:
|
||||
logger.debug(
|
||||
"Section from %d to %d, lowest at %d", start, end, lowest)
|
||||
|
||||
self.layout.addRow(
|
||||
"Resonance",
|
||||
QtWidgets.QLabel(
|
||||
f"{format_frequency(self.app.data11[lowest].freq)}"
|
||||
f" ({format_complex_imp(self.app.data11[lowest].impedance())})"))
|
||||
else:
|
||||
self.layout.addRow("Resonance", QtWidgets.QLabel(
|
||||
format_frequency(self.app.data11[lowest].freq)))
|
||||
self.layout.addWidget(PeakSearchAnalysis.QHLine())
|
||||
# Remove the final separator line
|
||||
self.layout.removeRow(self.layout.rowCount() - 1)
|
||||
if filename and extended_data:
|
||||
|
||||
with open(filename, 'w', newline='') as csvfile:
|
||||
fieldnames = extended_data[0].keys()
|
||||
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||
|
||||
writer.writeheader()
|
||||
for row in extended_data:
|
||||
writer.writerow(row)
|
||||
|
||||
else:
|
||||
self.layout.addRow(QtWidgets.QLabel(
|
||||
"No resonance found"))
|
||||
|
||||
|
||||
class EFHWAnalysis(ResonanceAnalysis):
|
||||
'''
|
||||
find only resonance when HI impedance
|
||||
'''
|
||||
old_data = []
|
||||
|
||||
def reset(self):
|
||||
logger.debug("reset")
|
||||
|
||||
def runAnalysis(self):
|
||||
self.reset()
|
||||
# self.results_label = QtWidgets.QLabel("<b>Results</b>")
|
||||
# max_dips_shown = self.max_dips_shown
|
||||
description = self.input_description.text()
|
||||
if description:
|
||||
filename = os.path.join("/tmp/", "{}.csv".format(description))
|
||||
else:
|
||||
filename = None
|
||||
|
||||
crossing = self._get_crossing()
|
||||
|
||||
data = []
|
||||
for d in self.app.data11:
|
||||
data.append(d.impedance().real)
|
||||
|
||||
maximums = sorted(self.find_maximums(data, threshold=500))
|
||||
|
||||
results_header = self.layout.indexOf(self.results_label)
|
||||
logger.debug("Results start at %d, out of %d",
|
||||
results_header, self.layout.rowCount())
|
||||
for i in range(results_header, self.layout.rowCount()):
|
||||
self.layout.removeRow(self.layout.rowCount() - 1)
|
||||
|
||||
extended_data = OrderedDict()
|
||||
|
||||
#both = np.intersect1d([i[1] for i in crossing], maximums)
|
||||
both = []
|
||||
|
||||
tolerance = 2
|
||||
for i in maximums:
|
||||
for l, _, h in crossing:
|
||||
if l - tolerance <= i <= h + tolerance:
|
||||
both.append(i)
|
||||
continue
|
||||
if l > i:
|
||||
continue
|
||||
|
||||
if both:
|
||||
logger.info("%i crossing HW", len(both))
|
||||
logger.info(crossing)
|
||||
logger.info(maximums)
|
||||
logger.info(both)
|
||||
for m in both:
|
||||
my_data = self._get_data(m)
|
||||
if m in extended_data:
|
||||
extended_data[m].update(my_data)
|
||||
else:
|
||||
extended_data[m] = my_data
|
||||
for i in range(min(len(both), len(self.app.markers))):
|
||||
|
||||
# self.app.markers[i].label = {}
|
||||
# for l in TYPES:
|
||||
# self.app.markers[i][l.label_id] = MarkerLabel(l.name)
|
||||
# self.app.markers[i].label['actualfreq'].setMinimumWidth(
|
||||
# 100)
|
||||
# self.app.markers[i].label['returnloss'].setMinimumWidth(80)
|
||||
|
||||
self.app.markers[i].setFrequency(
|
||||
str(self.app.data11[both[i]].freq))
|
||||
self.app.markers[i].frequencyInput.setText(
|
||||
str(self.app.data11[both[i]].freq))
|
||||
else:
|
||||
logger.info("TO DO: find near data")
|
||||
for m in crossing:
|
||||
start, lowest, end = m
|
||||
my_data = self._get_data(lowest)
|
||||
|
||||
if lowest in extended_data:
|
||||
extended_data[lowest].update(my_data)
|
||||
else:
|
||||
extended_data[lowest] = my_data
|
||||
|
||||
logger.debug("maximumx %s of type %s", maximums, type(maximums))
|
||||
for m in maximums:
|
||||
logger.debug("m %s of type %s", m, type(m))
|
||||
|
||||
my_data = self._get_data(m)
|
||||
if m in extended_data:
|
||||
extended_data[m].update(my_data)
|
||||
else:
|
||||
extended_data[m] = my_data
|
||||
|
||||
# saving and comparing
|
||||
|
||||
fields = [("freq", format_frequency_short),
|
||||
("r", format_resistence_neg),
|
||||
("lambda", round_2),
|
||||
]
|
||||
if self.old_data:
|
||||
diff = self.compare(
|
||||
self.old_data[-1], extended_data, fields=fields)
|
||||
else:
|
||||
diff = self.compare({}, extended_data, fields=fields)
|
||||
self.old_data.append(extended_data)
|
||||
|
||||
for i, index in enumerate(sorted(extended_data.keys())):
|
||||
|
||||
self.layout.addRow(
|
||||
f"{format_frequency_short(self.app.data11[index].freq)}",
|
||||
QtWidgets.QLabel(f" ({diff[i]['freq']})"
|
||||
f" {format_complex_imp(self.app.data11[index].impedance())}"
|
||||
f" ({diff[i]['r']})"
|
||||
f" {diff[i]['lambda']} m"))
|
||||
|
||||
# Remove the final separator line
|
||||
# self.layout.removeRow(self.layout.rowCount() - 1)
|
||||
if filename and extended_data:
|
||||
|
||||
with open(filename, 'w', newline='') as csvfile:
|
||||
fieldnames = extended_data[sorted(
|
||||
extended_data.keys())[0]].keys()
|
||||
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||
|
||||
writer.writeheader()
|
||||
for index in sorted(extended_data.keys()):
|
||||
row = extended_data[index]
|
||||
writer.writerow(row)
|
||||
|
||||
def compare(self, old, new, fields=[("freq", str), ]):
|
||||
'''
|
||||
Compare data to help changes
|
||||
|
||||
NB
|
||||
must be same sweep
|
||||
( same index must be same frequence )
|
||||
:param old:
|
||||
:param new:
|
||||
'''
|
||||
|
||||
def no_compare():
|
||||
|
||||
return {k: "-" for k, _ in fields}
|
||||
|
||||
old_idx = sorted(old.keys())
|
||||
# 'odict_keys' object is not subscriptable
|
||||
new_idx = sorted(new.keys())
|
||||
diff = {}
|
||||
i_max = min(len(old_idx), len(new_idx))
|
||||
i_tot = max(len(old_idx), len(new_idx))
|
||||
|
||||
if i_max == i_tot:
|
||||
logger.debug("may be the same antenna ... analyzing")
|
||||
|
||||
else:
|
||||
logger.warning("resonances changed from %s to %s",
|
||||
len(old_idx), len(new_idx))
|
||||
|
||||
logger.debug("Trying to compare only first %s resonances", i_max)
|
||||
|
||||
split = 0
|
||||
max_delta_f = 1000000 # 1M
|
||||
for i, k in enumerate(new_idx):
|
||||
my_diff = {}
|
||||
|
||||
logger.info("Risonance %s at %s", i,
|
||||
format_frequency(new[k]["freq"]))
|
||||
|
||||
if len(old_idx) <= i + split:
|
||||
diff[i] = no_compare()
|
||||
continue
|
||||
|
||||
delta_f = new[k]["freq"] - old[old_idx[i + split]]["freq"]
|
||||
if abs(delta_f) < max_delta_f:
|
||||
logger.debug("can compare")
|
||||
|
||||
else:
|
||||
logger.debug("can't compare, %s is too much ",
|
||||
format_frequency(delta_f))
|
||||
if delta_f > 0:
|
||||
|
||||
logger.debug("possible missing band, ")
|
||||
if (len(old_idx) > (i + split + 1)):
|
||||
if abs(new[k]["freq"] - old[old_idx[i + split + 1]]["freq"]) < max_delta_f:
|
||||
logger.debug("new is missing band, compare next ")
|
||||
split += 1
|
||||
# FIXME: manage 2 or more band missing ?!?
|
||||
else:
|
||||
logger.debug("new band, non compare ")
|
||||
diff[i] = no_compare()
|
||||
continue
|
||||
else:
|
||||
logger.debug("new band, non compare ")
|
||||
diff[i] = no_compare()
|
||||
|
||||
split -= 1
|
||||
continue
|
||||
|
||||
for d, fn in fields:
|
||||
my_diff[d] = fn(new[k][d] - old[old_idx[i + split]][d])
|
||||
logger.info("Delta %s = %s", d,
|
||||
my_diff[d])
|
||||
|
||||
diff[i] = my_diff
|
||||
|
||||
for i in range(i_max, i_tot):
|
||||
# add missing in old ... if any
|
||||
|
||||
diff[i] = no_compare()
|
||||
|
||||
return diff
|
||||
|
|
|
@ -39,6 +39,7 @@ class Datapoint(NamedTuple):
|
|||
@property
|
||||
def z(self) -> complex:
|
||||
""" return the datapoint impedance as complex number """
|
||||
# FIXME: not impedance, but s11 ?
|
||||
return complex(self.re, self.im)
|
||||
|
||||
@property
|
||||
|
@ -158,7 +159,7 @@ def corr_att_data(data: List[Datapoint], att: float) -> List[Datapoint]:
|
|||
if att <= 0:
|
||||
return data
|
||||
else:
|
||||
att = 10**(att/20)
|
||||
att = 10**(att / 20)
|
||||
ndata = []
|
||||
for dp in data:
|
||||
corrected = dp.z * att
|
||||
|
|
|
@ -23,6 +23,9 @@ from PyQt5 import QtWidgets, QtCore
|
|||
from NanoVNASaver.Analysis import Analysis, LowPassAnalysis, HighPassAnalysis, \
|
||||
BandPassAnalysis, BandStopAnalysis, VSWRAnalysis, \
|
||||
SimplePeakSearchAnalysis, MagLoopAnalysis
|
||||
from NanoVNASaver.Analysis.VSWRAnalysis import ResonanceAnalysis
|
||||
from NanoVNASaver.Analysis.VSWRAnalysis import EFHWAnalysis
|
||||
from NanoVNASaver.Analysis import PeakSearchAnalysis
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -46,14 +49,25 @@ class AnalysisWindow(QtWidgets.QWidget):
|
|||
select_analysis_box = QtWidgets.QGroupBox("Select analysis")
|
||||
select_analysis_layout = QtWidgets.QFormLayout(select_analysis_box)
|
||||
self.analysis_list = QtWidgets.QComboBox()
|
||||
self.analysis_list.addItem("Low-pass filter", LowPassAnalysis(self.app))
|
||||
self.analysis_list.addItem("Band-pass filter", BandPassAnalysis(self.app))
|
||||
self.analysis_list.addItem("High-pass filter", HighPassAnalysis(self.app))
|
||||
self.analysis_list.addItem("Band-stop filter", BandStopAnalysis(self.app))
|
||||
# self.analysis_list.addItem("Peak search", PeakSearchAnalysis(self.app))
|
||||
self.analysis_list.addItem("Peak search", SimplePeakSearchAnalysis(self.app))
|
||||
self.analysis_list.addItem(
|
||||
"Low-pass filter", LowPassAnalysis(self.app))
|
||||
self.analysis_list.addItem(
|
||||
"Band-pass filter", BandPassAnalysis(self.app))
|
||||
self.analysis_list.addItem(
|
||||
"High-pass filter", HighPassAnalysis(self.app))
|
||||
self.analysis_list.addItem(
|
||||
"Band-stop filter", BandStopAnalysis(self.app))
|
||||
self.analysis_list.addItem(
|
||||
"Simple Peak search", SimplePeakSearchAnalysis(self.app))
|
||||
self.analysis_list.addItem(
|
||||
"Peak search", PeakSearchAnalysis(self.app))
|
||||
self.analysis_list.addItem("VSWR analysis", VSWRAnalysis(self.app))
|
||||
self.analysis_list.addItem("MagLoop analysis", MagLoopAnalysis(self.app))
|
||||
self.analysis_list.addItem(
|
||||
"Resonance analysis", ResonanceAnalysis(self.app))
|
||||
self.analysis_list.addItem(
|
||||
"HWEF analysis", EFHWAnalysis(self.app))
|
||||
self.analysis_list.addItem(
|
||||
"MagLoop analysis", MagLoopAnalysis(self.app))
|
||||
select_analysis_layout.addRow("Analysis type", self.analysis_list)
|
||||
self.analysis_list.currentIndexChanged.connect(self.updateSelection)
|
||||
|
||||
|
@ -61,8 +75,10 @@ class AnalysisWindow(QtWidgets.QWidget):
|
|||
btn_run_analysis.clicked.connect(self.runAnalysis)
|
||||
select_analysis_layout.addRow(btn_run_analysis)
|
||||
|
||||
self.checkbox_run_automatically = QtWidgets.QCheckBox("Run automatically")
|
||||
self.checkbox_run_automatically.stateChanged.connect(self.toggleAutomaticRun)
|
||||
self.checkbox_run_automatically = QtWidgets.QCheckBox(
|
||||
"Run automatically")
|
||||
self.checkbox_run_automatically.stateChanged.connect(
|
||||
self.toggleAutomaticRun)
|
||||
select_analysis_layout.addRow(self.checkbox_run_automatically)
|
||||
|
||||
analysis_box = QtWidgets.QGroupBox("Analysis")
|
||||
|
@ -87,7 +103,8 @@ class AnalysisWindow(QtWidgets.QWidget):
|
|||
old_item = self.analysis_layout.itemAt(0)
|
||||
if old_item is not None:
|
||||
old_widget = self.analysis_layout.itemAt(0).widget()
|
||||
self.analysis_layout.replaceWidget(old_widget, self.analysis.widget())
|
||||
self.analysis_layout.replaceWidget(
|
||||
old_widget, self.analysis.widget())
|
||||
old_widget.hide()
|
||||
else:
|
||||
self.analysis_layout.addWidget(self.analysis.widget())
|
||||
|
|
Ładowanie…
Reference in New Issue