Simplified analysis

pull/550/head
Holger Müller 2022-09-20 19:52:34 +02:00
rodzic 01eb028f9f
commit d1ea20f989
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 2FDB49E81EAE6622
13 zmienionych plików z 300 dodań i 718 usunięć

Wyświetl plik

@ -1,7 +1,8 @@
# NanoVNASaver
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2020,2021 NanoVNA-Saver Authors
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020ff NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -16,14 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from PyQt5.Qt import QTimer
'''
Created on May 30th 2020
@author: mauro
'''
import logging
from time import sleep
from PyQt5 import QtWidgets
@ -67,6 +63,7 @@ class MagLoopAnalysis(VSWRAnalysis):
self.layout.addRow("", QtWidgets.QLabel(
"Multiple minimums, not magloop or try to lower VSWR limit"))
return
if len(self.minimums) == 1:
m = self.minimums[0]
start, lowest, end = m
@ -104,6 +101,7 @@ class MagLoopAnalysis(VSWRAnalysis):
logger.debug(
"no minimum found, looking for higher value %s",
self.vswr_limit_value)
new_start = max(self.min_freq, new_start)
new_end = min(self.max_freq, new_end)
logger.debug("next search will be %s - %s for vswr %s",
@ -113,16 +111,9 @@ class MagLoopAnalysis(VSWRAnalysis):
self.app.sweep_control.set_start(new_start)
self.app.sweep_control.set_end(new_end)
# set timer to let finish all stuff before new sweep
QTimer.singleShot(2000, self._safe_sweep)
def _safe_sweep(self):
"""
sweep only if button enabled
to prevent multiple/concurrent sweep
"""
# TODO: get info if sweep is running instead of just sleeping
# a guessed time
sleep(2.0)
if self.app.sweep_control.btn_start.isEnabled():
self.app.sweep_start()
else:
logger.error("sweep alredy running")

Wyświetl plik

@ -1,8 +1,8 @@
# NanoVNASaver
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020,2021 NanoVNA-Saver Authors
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020ff NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -18,36 +18,29 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
import math
from typing import Dict, List, Tuple
from typing import Dict, List
from PyQt5 import QtWidgets
import NanoVNASaver.AnalyticTools as at
from NanoVNASaver.Analysis.Base import Analysis
from NanoVNASaver.Analysis.Base import Analysis, CUTOFF_VALS
from NanoVNASaver.Formatting import format_frequency
logger = logging.getLogger(__name__)
CUTOFF_VALS = (3.0, 6.0, 10.0, 20.0, 60.0)
class BandPassAnalysis(Analysis):
def __init__(self, app):
super().__init__(app)
self._widget = QtWidgets.QWidget()
self.label = {
label: QtWidgets.QLabel() for label in
('titel', 'result', 'octave_l', 'octave_r', 'decade_l', 'decade_r',
'freq_center', 'span_3.0dB', 'span_6.0dB', 'q_factor')
}
for label in ('octave_l', 'octave_r', 'decade_l', 'decade_r',
'freq_center', 'span_3.0dB', 'span_6.0dB', 'q_factor'):
self.label[label] = QtWidgets.QLabel()
for attn in CUTOFF_VALS:
self.label[f"{attn:.1f}dB_l"] = QtWidgets.QLabel()
self.label[f"{attn:.1f}dB_r"] = QtWidgets.QLabel()
layout = QtWidgets.QFormLayout()
self._widget.setLayout(layout)
layout = self.layout
layout.addRow(self.label['titel'])
layout.addRow(
QtWidgets.QLabel(
@ -79,17 +72,10 @@ class BandPassAnalysis(Analysis):
self.set_titel("Band pass filter analysis")
def set_titel(self, name):
self.label['titel'].setText(name)
def reset(self):
for label in self.label.values():
label.clear()
def runAnalysis(self):
if not self.app.data.s21:
logger.debug("No data to analyse")
self.label['result'].setText("No data to analyse.")
self.set_result("No data to analyse.")
return
self.reset()
@ -124,10 +110,10 @@ class BandPassAnalysis(Analysis):
}
result['q_factor'] = result['freq_center'] / result['span_3.0dB']
result['octave_l'], result['decade_l'] = self.calculateRolloff(
cutoff_pos["10.0dB_l"], cutoff_pos["20.0dB_l"])
result['octave_r'], result['decade_r'] = self.calculateRolloff(
cutoff_pos["10.0dB_r"], cutoff_pos["20.0dB_r"])
result['octave_l'], result['decade_l'] = at.calculate_rolloff(
s21, cutoff_pos["10.0dB_l"], cutoff_pos["20.0dB_l"])
result['octave_r'], result['decade_r'] = at.calculate_rolloff(
s21, cutoff_pos["10.0dB_r"], cutoff_pos["20.0dB_r"])
for label, val in cutoff_freq.items():
self.label[label].setText(
@ -141,22 +127,18 @@ class BandPassAnalysis(Analysis):
self.label[label].setText(f"{result[label]:.3f}dB/{label[:-2]}")
self.app.markers[0].setFrequency(f"{result['freq_center']}")
self.app.markers[0].frequencyInput.setText(f"{result['freq_center']}")
self.app.markers[1].setFrequency(f"{cutoff_freq['3.0dB_l']}")
self.app.markers[1].frequencyInput.setText(f"{cutoff_freq['3.0dB_l']}")
self.app.markers[2].setFrequency(f"{cutoff_freq['3.0dB_r']}")
self.app.markers[2].frequencyInput.setText(f"{cutoff_freq['3.0dB_r']}")
if cutoff_gain['3.0dB_l'] < -4 or cutoff_gain['3.0dB_r'] < -4:
logger.warning(
"Data points insufficient for true -3 dB points."
"Cutoff gains: %fdB, %fdB", cutoff_gain['3.0dB_l'], cutoff_gain['3.0dB_r'])
self.label['result'].setText(
self.set_result(
f"Analysis complete ({len(s21)} points)\n"
f"Insufficient data for analysis. Increase segment count.")
return
self.label['result'].setText(
f"Analysis complete ({len(s21)} points)")
self.set_result(f"Analysis complete ({len(s21)} points)")
def derive_60dB(self,
cutoff_pos: Dict[str, int],
@ -186,13 +168,12 @@ class BandPassAnalysis(Analysis):
if marker.location <= 0 or marker.location >= len(gains) - 1:
logger.debug("No valid location for %s (%s)",
marker.name, marker.location)
self.label['result'].setText(
f"Please place {marker.name} in the passband.")
self.set_result(f"Please place {marker.name} in the passband.")
return -1
# find center of passband based on marker pos
if (peak := at.center_from_idx(gains, marker.location)) < 0:
self.label['result'].setText("Bandpass center not found")
self.set_result("Bandpass center not found")
return -1
return peak

Wyświetl plik

@ -20,8 +20,8 @@ import logging
from typing import Dict, List
import NanoVNASaver.AnalyticTools as at
from NanoVNASaver.Analysis.BandPassAnalysis import (
BandPassAnalysis, CUTOFF_VALS)
from NanoVNASaver.Analysis.Base import CUTOFF_VALS
from NanoVNASaver.Analysis.BandPassAnalysis import BandPassAnalysis
logger = logging.getLogger(__name__)

Wyświetl plik

@ -1,8 +1,8 @@
# NanoVNASaver
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020,2021 NanoVNA-Saver Authors
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020ff NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@ -17,16 +17,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
import math
from typing import Tuple
import numpy as np
import scipy
from typing import Dict
from PyQt5 import QtWidgets
logger = logging.getLogger(__name__)
CUTOFF_VALS = (3.0, 6.0, 10.0, 20.0, 60.0)
class QHLine(QtWidgets.QFrame):
def __init__(self):
@ -35,100 +32,15 @@ class QHLine(QtWidgets.QFrame):
class Analysis:
_widget = None
@classmethod
def find_crossing_zero(cls, data):
"""
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
"""
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, _ = scipy.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
return [k for k in peaks if data[k] > threshold]
def __init__(self, app: QtWidgets.QWidget):
self.app = app
self.label: Dict[str, QtWidgets.QLabel] = {
'titel': QtWidgets.QLabel(),
'result': QtWidgets.QLabel(),
}
self.layout = QtWidgets.QFormLayout()
self._widget = QtWidgets.QWidget()
self._widget.setLayout(self.layout)
def widget(self) -> QtWidgets.QWidget:
return self._widget
@ -137,20 +49,11 @@ class Analysis:
pass
def reset(self):
pass
for label in self.label.values():
label.clear()
def calculateRolloff(self, idx_1: int, idx_2: int) -> Tuple[float, float]:
if idx_1 == idx_2:
return (math.nan, math.nan)
s21 = self.app.data.s21
freq_1 = s21[idx_1].freq
freq_2 = s21[idx_2].freq
gain1 = s21[idx_1].gain
gain2 = s21[idx_2].gain
factor = freq_1 / freq_2 if freq_1 > freq_2 else freq_2 / freq_1
attn = abs(gain1 - gain2)
logger.debug("Measured points: %d Hz and %d Hz\n%fdB over %f factor",
freq_1, freq_2, attn, factor)
octave_attn = attn / (math.log10(factor) / math.log10(2))
decade_attn = attn / math.log10(factor)
return (octave_attn, decade_attn)
def set_result(self, text):
self.label['result'].setText(text)
def set_titel(self, text):
self.label['titel'].setText(text)

Wyświetl plik

@ -18,10 +18,12 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
import math
from typing import Dict, List
from PyQt5 import QtWidgets
from NanoVNASaver.Analysis.Base import Analysis
import NanoVNASaver.AnalyticTools as at
from NanoVNASaver.Analysis.Base import Analysis, CUTOFF_VALS
from NanoVNASaver.Formatting import format_frequency
logger = logging.getLogger(__name__)
@ -31,174 +33,89 @@ class HighPassAnalysis(Analysis):
def __init__(self, app):
super().__init__(app)
self._widget = QtWidgets.QWidget()
self.label["octave"] = QtWidgets.QLabel()
self.label["decade"] = QtWidgets.QLabel()
for attn in CUTOFF_VALS:
self.label[f"{attn:.1f}dB"] = QtWidgets.QLabel()
self.label[f"{attn:.1f}dB"] = QtWidgets.QLabel()
layout = QtWidgets.QFormLayout()
self._widget.setLayout(layout)
layout.addRow(QtWidgets.QLabel("High pass filter analysis"))
layout = self.layout
layout.addRow(self.label["titel"])
layout.addRow(QtWidgets.QLabel(
f"Please place {self.app.markers[0].name}"
f" in the filter passband."))
self.result_label = QtWidgets.QLabel()
self.cutoff_label = QtWidgets.QLabel()
self.six_db_label = QtWidgets.QLabel()
self.sixty_db_label = QtWidgets.QLabel()
self.db_per_octave_label = QtWidgets.QLabel()
self.db_per_decade_label = QtWidgets.QLabel()
layout.addRow("Result:", self.result_label)
layout.addRow("Cutoff frequency:", self.cutoff_label)
layout.addRow("-6 dB point:", self.six_db_label)
layout.addRow("-60 dB point:", self.sixty_db_label)
layout.addRow("Roll-off:", self.db_per_octave_label)
layout.addRow("Roll-off:", self.db_per_decade_label)
layout.addRow("Result:", self.label["result"])
layout.addRow("Cutoff frequency:", self.label["3.0dB"])
layout.addRow("-6 dB point:", self.label["6.0dB"])
layout.addRow("-60 dB point:", self.label["60.0dB"])
layout.addRow("Roll-off:", self.label["octave"])
layout.addRow("Roll-off:", self.label["decade"])
def reset(self):
self.result_label.clear()
self.cutoff_label.clear()
self.six_db_label.clear()
self.sixty_db_label.clear()
self.db_per_octave_label.clear()
self.db_per_decade_label.clear()
self.set_titel('Highpass analysis')
def runAnalysis(self):
self.reset()
pass_band_location = self.app.markers[0].location
logger.debug("Pass band location: %d", pass_band_location)
if len(self.app.data.s21) == 0:
if not self.app.data.s21:
logger.debug("No data to analyse")
self.result_label.setText("No data to analyse.")
return
if pass_band_location < 0:
logger.debug("No location for %s", self.app.markers[0].name)
self.result_label.setText(
f"Please place {self.app.markers[0].name } in the passband.")
self.reset()
s21 = self.app.data.s21
gains = [d.gain for d in s21]
if (peak := self.find_level(gains)) < 0:
return
peak_db = gains[peak]
logger.debug("Passband position: %d(%fdB)", peak, peak_db)
pass_band_db = self.app.data.s21[pass_band_location].gain
cutoff_pos = self.find_cutoffs(gains, peak, peak_db)
cutoff_freq = {
att: s21[val].freq if val >= 0 else math.nan
for att, val in cutoff_pos.items()
}
cutoff_gain = {
att: gains[val] if val >= 0 else math.nan
for att, val in cutoff_pos.items()
}
logger.debug("Cuttoff frequencies: %s", cutoff_freq)
logger.debug("Cuttoff gains: %s", cutoff_gain)
logger.debug("Initial passband gain: %d", pass_band_db)
octave, decade = at.calculate_rolloff(
s21, cutoff_pos["10.0dB"], cutoff_pos["20.0dB"])
initial_cutoff_location = -1
for i in range(pass_band_location, -1, -1):
db = self.app.data.s21[i].gain
if (pass_band_db - db) > 3:
# We found a cutoff location
initial_cutoff_location = i
break
if initial_cutoff_location < 0:
self.result_label.setText("Cutoff location not found.")
return
initial_cutoff_frequency = (
self.app.data.s21[initial_cutoff_location].freq)
logger.debug("Found initial cutoff frequency at %d",
initial_cutoff_frequency)
peak_location = -1
peak_db = self.app.data.s21[initial_cutoff_location].gain
for i in range(len(self.app.data.s21) - 1,
initial_cutoff_location - 1, -1):
if self.app.data.s21[i].gain > peak_db:
peak_db = db
peak_location = i
logger.debug("Found peak of %f at %d", peak_db,
self.app.data.s11[peak_location].freq)
self.app.markers[0].setFrequency(
str(self.app.data.s21[peak_location].freq))
self.app.markers[0].frequencyInput.setText(
str(self.app.data.s21[peak_location].freq))
cutoff_location = -1
pass_band_db = peak_db
for i in range(peak_location, -1, -1):
if (pass_band_db - self.app.data.s21[i].gain) > 3:
# We found the cutoff location
cutoff_location = i
break
cutoff_frequency = self.app.data.s21[cutoff_location].freq
cutoff_gain = self.app.data.s21[cutoff_location].gain - pass_band_db
if cutoff_gain < -4:
if cutoff_gain['3.0dB'] < -4:
logger.debug("Cutoff frequency found at %f dB"
" - insufficient data points for true -3 dB point.",
cutoff_gain)
logger.debug("Found true cutoff frequency at %d", cutoff_frequency)
logger.debug("Found true cutoff frequency at %d", cutoff_freq['3.0dB'])
self.cutoff_label.setText(
f"{format_frequency(cutoff_frequency)}"
f" {round(cutoff_gain, 1)} dB)")
self.app.markers[1].setFrequency(str(cutoff_frequency))
self.app.markers[1].frequencyInput.setText(str(cutoff_frequency))
for label, val in cutoff_freq.items():
self.label[label].setText(
f"{format_frequency(val)}"
f" ({cutoff_gain[label]:.1f} dB)")
six_db_location = -1
for i in range(cutoff_location, -1, -1):
if (pass_band_db - self.app.data.s21[i].gain) > 6:
# We found 6dB location
six_db_location = i
break
self.label['octave'].setText(f'{octave:.3f}dB/octave')
self.label['decade'].setText(f'{decade:.3f}dB/decade')
if six_db_location < 0:
self.result_label.setText("6 dB location not found.")
return
six_db_cutoff_frequency = self.app.data.s21[six_db_location].freq
self.six_db_label.setText(
format_frequency(six_db_cutoff_frequency))
self.app.markers[0].setFrequency(str(s21[peak].freq))
self.app.markers[1].setFrequency(str(cutoff_freq['3.0dB']))
self.app.markers[2].setFrequency(str(cutoff_freq['6.0dB']))
ten_db_location = -1
for i in range(cutoff_location, -1, -1):
if (pass_band_db - self.app.data.s21[i].gain) > 10:
# We found 6dB location
ten_db_location = i
break
self.set_result(f"Analysis complete ({len(s21)}) points)")
twenty_db_location = -1
for i in range(cutoff_location, -1, -1):
if (pass_band_db - self.app.data.s21[i].gain) > 20:
# We found 6dB location
twenty_db_location = i
break
def find_level(self, gains: List[float]) -> int:
marker = self.app.markers[0]
logger.debug("Pass band location: %d", marker.location)
if marker.location < 0:
self.set_result(f"Please place {marker.name} in the passband.")
return -1
return at.center_from_idx(gains, marker.location)
sixty_db_location = -1
for i in range(six_db_location, -1, -1):
if (pass_band_db - self.app.data.s21[i].gain) > 60:
# We found 60dB location! Wow.
sixty_db_location = i
break
if sixty_db_location > 0:
if sixty_db_location > 0:
sixty_db_cutoff_frequency = (
self.app.data.s21[sixty_db_location].freq)
self.sixty_db_label.setText(
format_frequency(sixty_db_cutoff_frequency))
elif ten_db_location != -1 and twenty_db_location != -1:
ten = self.app.data.s21[ten_db_location].freq
twenty = self.app.data.s21[twenty_db_location].freq
sixty_db_frequency = ten * \
10 ** (5 * (math.log10(twenty) - math.log10(ten)))
self.sixty_db_label.setText(
f"{format_frequency(sixty_db_frequency)} (derived)")
else:
self.sixty_db_label.setText("Not calculated")
if (ten_db_location > 0 and
twenty_db_location > 0 and
ten_db_location != twenty_db_location):
octave_attenuation, decade_attenuation = self.calculateRolloff(
ten_db_location, twenty_db_location)
self.db_per_octave_label.setText(
str(round(octave_attenuation, 3)) + " dB / octave")
self.db_per_decade_label.setText(
str(round(decade_attenuation, 3)) + " dB / decade")
else:
self.db_per_octave_label.setText("Not calculated")
self.db_per_decade_label.setText("Not calculated")
self.result_label.setText(
f"Analysis complete ({len(self.app.data.s11)}) points)")
def find_cutoffs(self,
gains: List[float],
peak: int, peak_db: float) -> Dict[str, int]:
return {
f"{attn:.1f}dB": at.cut_off_left(
gains, peak, peak_db, attn)
for attn in CUTOFF_VALS
}

Wyświetl plik

@ -17,195 +17,26 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
import math
from typing import Dict, List
from PyQt5 import QtWidgets
from NanoVNASaver.Analysis.Base import Analysis
from NanoVNASaver.Formatting import format_frequency
import NanoVNASaver.AnalyticTools as at
from NanoVNASaver.Analysis.Base import CUTOFF_VALS
from NanoVNASaver.Analysis.HighPassAnalysis import HighPassAnalysis
logger = logging.getLogger(__name__)
class LowPassAnalysis(Analysis):
class LowPassAnalysis(HighPassAnalysis):
def __init__(self, app):
super().__init__(app)
self._widget = QtWidgets.QWidget()
self.set_titel('Lowpass filter analysis')
layout = QtWidgets.QFormLayout()
self._widget.setLayout(layout)
layout.addRow(QtWidgets.QLabel("Low pass filter analysis"))
layout.addRow(
QtWidgets.QLabel(
f"Please place {self.app.markers[0].name}"
f" in the filter passband."))
self.result_label = QtWidgets.QLabel()
self.cutoff_label = QtWidgets.QLabel()
self.six_db_label = QtWidgets.QLabel()
self.sixty_db_label = QtWidgets.QLabel()
self.db_per_octave_label = QtWidgets.QLabel()
self.db_per_decade_label = QtWidgets.QLabel()
layout.addRow("Result:", self.result_label)
layout.addRow("Cutoff frequency:", self.cutoff_label)
layout.addRow("-6 dB point:", self.six_db_label)
layout.addRow("-60 dB point:", self.sixty_db_label)
layout.addRow("Roll-off:", self.db_per_octave_label)
layout.addRow("Roll-off:", self.db_per_decade_label)
def reset(self):
self.result_label.clear()
self.cutoff_label.clear()
self.six_db_label.clear()
self.sixty_db_label.clear()
self.db_per_octave_label.clear()
self.db_per_decade_label.clear()
def runAnalysis(self):
self.reset()
pass_band_location = self.app.markers[0].location
logger.debug("Pass band location: %d", pass_band_location)
if len(self.app.data.s21) == 0:
logger.debug("No data to analyse")
self.result_label.setText("No data to analyse.")
return
if pass_band_location < 0:
logger.debug("No location for %s",
self.app.markers[0].name)
self.result_label.setText(
f"Please place {self.app.markers[0].name} in the passband.")
return
pass_band_db = self.app.data.s21[pass_band_location].gain
logger.debug("Initial passband gain: %d", pass_band_db)
initial_cutoff_location = -1
for i in range(pass_band_location, len(self.app.data.s21)):
db = self.app.data.s21[i].gain
if (pass_band_db - db) > 3:
# We found a cutoff location
initial_cutoff_location = i
break
if initial_cutoff_location < 0:
self.result_label.setText("Cutoff location not found.")
return
initial_cutoff_frequency = (
self.app.data.s21[initial_cutoff_location].freq)
logger.debug("Found initial cutoff frequency at %d",
initial_cutoff_frequency)
peak_location = -1
peak_db = self.app.data.s21[initial_cutoff_location].gain
for i in range(0, initial_cutoff_location):
db = self.app.data.s21[i].gain
if db > peak_db:
peak_db = db
peak_location = i
logger.debug("Found peak of %f at %d", peak_db,
self.app.data.s11[peak_location].freq)
self.app.markers[0].setFrequency(
str(self.app.data.s21[peak_location].freq))
self.app.markers[0].frequencyInput.setText(
str(self.app.data.s21[peak_location].freq))
cutoff_location = -1
pass_band_db = peak_db
for i in range(peak_location, len(self.app.data.s21)):
db = self.app.data.s21[i].gain
if (pass_band_db - db) > 3:
# We found the cutoff location
cutoff_location = i
break
cutoff_frequency = self.app.data.s21[cutoff_location].freq
cutoff_gain = self.app.data.s21[cutoff_location].gain - pass_band_db
if cutoff_gain < -4:
logger.debug(
"Cutoff frequency found at %f dB"
" - insufficient data points for true -3 dB point.",
cutoff_gain)
logger.debug("Found true cutoff frequency at %d", cutoff_frequency)
self.cutoff_label.setText(
f"{format_frequency(cutoff_frequency)}"
f" ({round(cutoff_gain, 1)} dB)")
self.app.markers[1].setFrequency(str(cutoff_frequency))
self.app.markers[1].frequencyInput.setText(str(cutoff_frequency))
six_db_location = -1
for i in range(cutoff_location, len(self.app.data.s21)):
db = self.app.data.s21[i].gain
if (pass_band_db - db) > 6:
# We found 6dB location
six_db_location = i
break
if six_db_location < 0:
self.result_label.setText("6 dB location not found.")
return
six_db_cutoff_frequency = self.app.data.s21[six_db_location].freq
self.six_db_label.setText(
format_frequency(six_db_cutoff_frequency))
ten_db_location = -1
for i in range(cutoff_location, len(self.app.data.s21)):
db = self.app.data.s21[i].gain
if (pass_band_db - db) > 10:
# We found 6dB location
ten_db_location = i
break
twenty_db_location = -1
for i in range(cutoff_location, len(self.app.data.s21)):
db = self.app.data.s21[i].gain
if (pass_band_db - db) > 20:
# We found 6dB location
twenty_db_location = i
break
sixty_db_location = -1
for i in range(six_db_location, len(self.app.data.s21)):
db = self.app.data.s21[i].gain
if (pass_band_db - db) > 60:
# We found 60dB location! Wow.
sixty_db_location = i
break
if sixty_db_location > 0:
sixty_db_cutoff_frequency = (
self.app.data.s21[sixty_db_location].freq)
self.sixty_db_label.setText(
format_frequency(sixty_db_cutoff_frequency))
elif ten_db_location != -1 and twenty_db_location != -1:
ten = self.app.data.s21[ten_db_location].freq
twenty = self.app.data.s21[twenty_db_location].freq
sixty_db_frequency = ten * \
10 ** (5 * (math.log10(twenty) - math.log10(ten)))
self.sixty_db_label.setText(
f"{format_frequency(sixty_db_frequency)} (derived)")
else:
self.sixty_db_label.setText("Not calculated")
if (ten_db_location > 0 and
twenty_db_location > 0 and
ten_db_location != twenty_db_location):
octave_attenuation, decade_attenuation = self.calculateRolloff(
ten_db_location, twenty_db_location)
self.db_per_octave_label.setText(
str(round(octave_attenuation, 3)) + " dB / octave")
self.db_per_decade_label.setText(
str(round(decade_attenuation, 3)) + " dB / decade")
else:
self.db_per_octave_label.setText("Not calculated")
self.db_per_decade_label.setText("Not calculated")
self.result_label.setText(
"Analysis complete (" + str(len(self.app.data.s11)) + " points)")
def find_cutoffs(self,
gains: List[float],
peak: int, peak_db: float) -> Dict[str, int]:
return {
f"{attn:.1f}dB": at.cut_off_right(
gains, peak, peak_db, attn)
for attn in CUTOFF_VALS
}

Wyświetl plik

@ -22,7 +22,10 @@ from PyQt5 import QtWidgets
import scipy
import numpy as np
from NanoVNASaver.Analysis.Base import Analysis
from NanoVNASaver.Analysis.Base import QHLine
from NanoVNASaver.Analysis.SimplePeakSearchAnalysis import (
SimplePeakSearchAnalysis)
from NanoVNASaver.Formatting import format_vswr
from NanoVNASaver.Formatting import format_gain
from NanoVNASaver.Formatting import format_resistance
@ -32,99 +35,64 @@ from NanoVNASaver.Formatting import format_frequency_short
logger = logging.getLogger(__name__)
class PeakSearchAnalysis(Analysis):
class QHLine(QtWidgets.QFrame):
def __init__(self):
super().__init__()
self.setFrameShape(QtWidgets.QFrame.HLine)
class PeakSearchAnalysis(SimplePeakSearchAnalysis):
def __init__(self, app):
super().__init__(app)
self._widget = QtWidgets.QWidget()
self.layout = QtWidgets.QFormLayout()
self._widget.setLayout(self.layout)
self.peak_cnt = QtWidgets.QSpinBox()
self.peak_cnt.setValue(1)
self.peak_cnt.setMinimum(1)
self.peak_cnt.setMaximum(10)
self.rbtn_data_group = QtWidgets.QButtonGroup()
self.rbtn_data_vswr = QtWidgets.QRadioButton("VSWR")
self.rbtn_data_resistance = QtWidgets.QRadioButton("Resistance")
self.rbtn_data_reactance = QtWidgets.QRadioButton("Reactance")
self.rbtn_data_s21_gain = QtWidgets.QRadioButton("S21 Gain")
self.rbtn_data_group.addButton(self.rbtn_data_vswr)
self.rbtn_data_group.addButton(self.rbtn_data_resistance)
self.rbtn_data_group.addButton(self.rbtn_data_reactance)
self.rbtn_data_group.addButton(self.rbtn_data_s21_gain)
self.rbtn_data_vswr.setChecked(True)
self.rbtn_peak_group = QtWidgets.QButtonGroup()
self.rbtn_peak_positive = QtWidgets.QRadioButton("Positive")
self.rbtn_peak_negative = QtWidgets.QRadioButton("Negative")
self.rbtn_peak_both = QtWidgets.QRadioButton("Both")
self.rbtn_peak_group.addButton(self.rbtn_peak_positive)
self.rbtn_peak_group.addButton(self.rbtn_peak_negative)
self.rbtn_peak_group.addButton(self.rbtn_peak_both)
self.rbtn_peak_positive.setChecked(True)
self.input_number_of_peaks = QtWidgets.QSpinBox()
self.input_number_of_peaks.setValue(1)
self.input_number_of_peaks.setMinimum(1)
self.input_number_of_peaks.setMaximum(10)
self.checkbox_move_markers = QtWidgets.QCheckBox()
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)
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("Max number of peaks", self.peak_cnt)
self.layout.addRow(QHLine())
self.layout.addRow(QtWidgets.QLabel("<b>Results</b>"))
self.results_header = self.layout.rowCount()
self.set_titel('Peak search')
def runAnalysis(self):
if not self.app.data.s11:
return
self.reset()
data = []
sign = 1
count = self.input_number_of_peaks.value()
if self.rbtn_data_vswr.isChecked():
s11 = self.app.data.s11
s21 = self.app.data.s21
if not s21:
self.button['gain'].setEnabled(False)
if self.button['gain'].isChecked():
self.button['vswr'].setChecked(True)
else:
self.button['gain'].setEnabled(True)
count = self.peak_cnt.value()
if self.button['vswr'].isChecked():
fn = format_vswr
data.extend(d.vswr for d in self.app.data.s11)
elif self.rbtn_data_s21_gain.isChecked():
data = [d.vswr for d in s11]
elif self.button['gain'].isChecked():
fn = format_gain
data.extend(d.gain for d in self.app.data.s21)
elif self.rbtn_data_resistance.isChecked():
data = [d.gain for d in s21]
elif self.button['resistance'].isChecked():
fn = format_resistance
data.extend(d.impedance().real for d in self.app.data.s11)
elif self.rbtn_data_reactance.isChecked():
fn = str
data.extend(d.impedance().imag for d in self.app.data.s11)
data = [d.impedance().real for d in s11]
elif self.button['reactance'].isChecked():
fn = format_resistance
data = [d.impedance().imag for d in s11]
else:
logger.warning("Searching for peaks on unknown data")
return
if self.rbtn_peak_positive.isChecked():
sign = 1
if self.button['peak_h'].isChecked():
peaks, _ = scipy.signal.find_peaks(
data, width=3, distance=3, prominence=1)
elif self.rbtn_peak_negative.isChecked():
elif self.button['peak_l'].isChecked():
sign = -1
data = [x * sign for x in data]
peaks, _ = scipy.signal.find_peaks(
data, width=3, distance=3, prominence=1)
# elif self.rbtn_peak_both.isChecked():
# peaks_max, _ = scipy.signal.find_peaks(
# data, width=3, distance=3, prominence=1)
# peaks_min, _ = scipy.signal.find_peaks(
# np.array(data)*-1, width=3, distance=3, prominence=1)
# peaks = np.concatenate((peaks_max, peaks_min))
else:
# Both is not yet in
logger.warning(
@ -147,22 +115,20 @@ class PeakSearchAnalysis(Analysis):
logger.debug("Index %d", i)
logger.debug("Prominence %f", prominences[i])
logger.debug("Index in sweep %d", peaks[i])
logger.debug("Frequency %d", self.app.data.s11[peaks[i]].freq)
logger.debug("Frequency %d", s11[peaks[i]].freq)
logger.debug("Value %f", sign * data[peaks[i]])
self.layout.addRow(
f"Freq"
f" {format_frequency_short(self.app.data.s11[peaks[i]].freq)}",
f" {format_frequency_short(s11[peaks[i]].freq)}",
QtWidgets.QLabel(f" value {fn(sign * data[peaks[i]])}"
))
if self.checkbox_move_markers.isChecked():
if self.button['move_marker'].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))):
self.app.markers[i].setFrequency(
str(self.app.data.s11[peaks[indices[i]]].freq))
self.app.markers[i].frequencyInput.setText(
str(self.app.data.s11[peaks[indices[i]]].freq))
str(s11[peaks[indices[i]]].freq))
max_val = -10**10
max_idx = -1

Wyświetl plik

@ -18,11 +18,11 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import os
import csv
import itertools
import logging
from PyQt5 import QtWidgets
import NanoVNASaver.AnalyticTools as at
from NanoVNASaver.Analysis.Base import Analysis, QHLine
from NanoVNASaver.Formatting import (
format_frequency, format_complex_imp,
@ -62,12 +62,14 @@ class ResonanceAnalysis(Analysis):
self.layout.addRow(self.results_label)
def _get_data(self, index):
my_data = {"freq": self.app.data.s11[index].freq,
"s11": self.app.data.s11[index].z,
"lambda": self.app.data.s11[index].wavelength,
"impedance": self.app.data.s11[index].impedance(),
"vswr": self.app.data.s11[index].vswr,
}
s11 = self.app.data.s11
my_data = {
"freq": s11[index].freq,
"s11": s11[index].z,
"lambda": s11[index].wavelength,
"impedance": s11[index].impedance(),
"vswr": s11[index].vswr,
}
my_data["vswr_49"] = vswr_transformed(
my_data["impedance"], 49)
my_data["vswr_4"] = vswr_transformed(
@ -79,7 +81,7 @@ class ResonanceAnalysis(Analysis):
def _get_crossing(self):
data = [d.phase for d in self.app.data.s11]
return sorted(self.find_crossing_zero(data))
return at.zero_crossings(data)
def runAnalysis(self):
self.reset()
@ -99,41 +101,40 @@ class ResonanceAnalysis(Analysis):
for _ in range(results_header, self.layout.rowCount()):
self.layout.removeRow(self.layout.rowCount() - 1)
if crossing:
extended_data = []
for m in crossing:
start, lowest, end = m
my_data = self._get_data(lowest)
s11_low = self.app.data.s11[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(s11_low.freq)}"
f" ({format_complex_imp(s11_low.impedance())})"))
else:
self.layout.addRow("Resonance", QtWidgets.QLabel(
format_frequency(self.app.data.s11[lowest].freq)))
self.layout.addWidget(QHLine())
# Remove the final separator line
self.layout.removeRow(self.layout.rowCount() - 1)
if filename and extended_data:
with open(filename, 'w', encoding='utf-8', 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:
if not crossing:
self.layout.addRow(QtWidgets.QLabel(
"No resonance found"))
extended_data = []
for m in crossing:
start, lowest, end = m
my_data = self._get_data(lowest)
s11_low = self.app.data.s11[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(s11_low.freq)}"
f" ({format_complex_imp(s11_low.impedance())})"))
else:
self.layout.addRow("Resonance", QtWidgets.QLabel(
format_frequency(self.app.data.s11[lowest].freq)))
self.layout.addWidget(QHLine())
# Remove the final separator line
self.layout.removeRow(self.layout.rowCount() - 1)
if filename and extended_data:
with open(filename, 'w', encoding='utf-8', newline='') as csvfile:
fieldnames = extended_data[0].keys()
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for row in extended_data:
writer.writerow(row)
class EFHWAnalysis(ResonanceAnalysis):
"""
@ -152,7 +153,7 @@ class EFHWAnalysis(ResonanceAnalysis):
filename = None
crossing = self._get_crossing()
data = [d.impedance().real for d in self.app.data.s11]
maximums = sorted(self.find_maximums(data, threshold=500))
maximums = sorted(at.maxima(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())
@ -160,47 +161,22 @@ class EFHWAnalysis(ResonanceAnalysis):
for _ in range(results_header, self.layout.rowCount()):
self.layout.removeRow(self.layout.rowCount() - 1)
extended_data = {}
both = []
tolerance = 2
for i, (low, _, high) in itertools.product(maximums, crossing):
if low - tolerance <= i <= high + tolerance:
both.append(i)
continue
if low > 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].setFrequency(
str(self.app.data.s11[both[i]].freq))
self.app.markers[i].frequencyInput.setText(
str(self.app.data.s11[both[i]].freq))
else:
logger.info("TO DO: find near data")
for _, lowest, _ in crossing:
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
logger.info("TO DO: find near data")
for lowest in crossing:
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
fields = [("freq", format_frequency_short),
("r", format_resistence_neg), ("lambda", lambda x: round(x, 2))]

Wyświetl plik

@ -31,80 +31,86 @@ logger = logging.getLogger(__name__)
class SimplePeakSearchAnalysis(Analysis):
def __init__(self, app):
super().__init__(app)
self._widget = QtWidgets.QWidget()
outer_layout = QtWidgets.QFormLayout()
self._widget.setLayout(outer_layout)
self.rbtn_data_group = QtWidgets.QButtonGroup()
self.rbtn_data_vswr = QtWidgets.QRadioButton("VSWR")
self.rbtn_data_resistance = QtWidgets.QRadioButton("Resistance")
self.rbtn_data_reactance = QtWidgets.QRadioButton("Reactance")
self.rbtn_data_s21_gain = QtWidgets.QRadioButton("S21 Gain")
self.rbtn_data_group.addButton(self.rbtn_data_vswr)
self.rbtn_data_group.addButton(self.rbtn_data_resistance)
self.rbtn_data_group.addButton(self.rbtn_data_reactance)
self.rbtn_data_group.addButton(self.rbtn_data_s21_gain)
self.label['peak_freq'] = QtWidgets.QLabel()
self.label['peak_db'] = QtWidgets.QLabel()
self.rbtn_data_s21_gain.setChecked(True)
self.button = {
'vswr': QtWidgets.QRadioButton("VSWR"),
'resistance': QtWidgets.QRadioButton("Resistance"),
'reactance': QtWidgets.QRadioButton("Reactance"),
'gain': QtWidgets.QRadioButton("S21 Gain"),
'peak_h': QtWidgets.QRadioButton("Highest value"),
'peak_l': QtWidgets.QRadioButton("Lowest value"),
'move_marker': QtWidgets.QCheckBox()
}
self.rbtn_peak_group = QtWidgets.QButtonGroup()
self.rbtn_peak_positive = QtWidgets.QRadioButton("Highest value")
self.rbtn_peak_negative = QtWidgets.QRadioButton("Lowest value")
self.rbtn_peak_group.addButton(self.rbtn_peak_positive)
self.rbtn_peak_group.addButton(self.rbtn_peak_negative)
self.button['gain'].setChecked(True)
self.button['peak_h'].setChecked(True)
self.rbtn_peak_positive.setChecked(True)
self.btn_group = {
'data': QtWidgets.QButtonGroup(),
'peak': QtWidgets.QButtonGroup(),
}
self.checkbox_move_marker = QtWidgets.QCheckBox()
for btn in ('vswr', 'resistance', 'reactance', 'gain'):
self.btn_group['data'].addButton(self.button[btn])
self.btn_group['peak'].addButton(self.button['peak_h'])
self.btn_group['peak'].addButton(self.button['peak_l'])
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(QHLine())
outer_layout.addRow("Peak type", self.rbtn_peak_positive)
outer_layout.addRow("", self.rbtn_peak_negative)
outer_layout.addRow(QHLine())
outer_layout.addRow("Move marker to peak", self.checkbox_move_marker)
outer_layout.addRow(QHLine())
layout = self.layout
layout.addRow(self.label['titel'])
layout.addRow(QHLine())
layout.addRow(QtWidgets.QLabel("<b>Settings</b>"))
layout.addRow("Data source", self.button['vswr'])
layout.addRow("", self.button['resistance'])
layout.addRow("", self.button['reactance'])
layout.addRow("", self.button['gain'])
layout.addRow(QHLine())
layout.addRow("Peak type", self.button['peak_h'])
layout.addRow("", self.button['peak_l'])
layout.addRow(QHLine())
layout.addRow("Move marker to peak", self.button['move_marker'])
layout.addRow(QHLine())
layout.addRow(self.label['result'])
layout.addRow("Peak frequency:", self.label['peak_freq'])
layout.addRow("Peak value:", self.label['peak_db'])
outer_layout.addRow(QtWidgets.QLabel("<b>Results</b>"))
self.peak_frequency = QtWidgets.QLabel()
self.peak_value = QtWidgets.QLabel()
outer_layout.addRow("Peak frequency:", self.peak_frequency)
outer_layout.addRow("Peak value:", self.peak_value)
self.set_titel('Simple peak search')
def runAnalysis(self):
if not self.app.data.s11:
return
s11 = self.app.data.s11
s21 = self.app.data.s21
if self.rbtn_data_vswr.isChecked():
if not s21:
self.button['gain'].setEnabled(False)
if self.button['gain'].isChecked():
self.button['vswr'].setChecked(True)
else:
self.button['gain'].setEnabled(True)
if self.button['vswr'].isChecked():
suffix = ""
data = [d.vswr for d in s11]
elif self.rbtn_data_resistance.isChecked():
elif self.button['resistance'].isChecked():
suffix = " \N{OHM SIGN}"
data = [d.impedance().real for d in s11]
elif self.rbtn_data_reactance.isChecked():
elif self.button['reactance'].isChecked():
suffix = " \N{OHM SIGN}"
data = [d.impedance().imag for d in s11]
elif self.rbtn_data_s21_gain.isChecked():
elif self.button['gain'].isChecked():
suffix = " dB"
data = [d.gain for d in s21]
else:
logger.warning("Searching for peaks on unknown data")
return
if len(data) == 0:
return
if self.rbtn_peak_positive.isChecked():
if self.button['peak_h'].isChecked():
idx_peak = np.argmax(data)
elif self.rbtn_peak_negative.isChecked():
elif self.button['peak_l'].isChecked():
idx_peak = np.argmin(data)
else:
# Both is not yet in
@ -113,12 +119,9 @@ class SimplePeakSearchAnalysis(Analysis):
" but neither looking at positive nor negative?")
return
self.peak_frequency.setText(
format_frequency(self.app.data.s11[idx_peak].freq))
self.peak_value.setText(str(round(data[idx_peak], 3)) + suffix)
self.label['peak_freq'].setText(
format_frequency(s11[idx_peak].freq))
self.label['peak_db'].setText(f"{round(data[idx_peak], 3)}{suffix}")
if self.checkbox_move_marker.isChecked() and self.app.markers:
self.app.markers[0].setFrequency(
str(self.app.data.s11[idx_peak].freq))
self.app.markers[0].frequencyInput.setText(
format_frequency(self.app.data.s11[idx_peak].freq))
if self.button['move_marker'].isChecked() and self.app.markers:
self.app.markers[0].setFrequency(f"{s11[idx_peak].freq}")

Wyświetl plik

@ -23,7 +23,7 @@ from typing import Callable, List, Tuple
import numpy as np
import scipy
import NanoVNASaver.AnalyticTools as at
from NanoVNASaver.RFTools import Datapoint
def zero_crossings(data: List[float]) -> List[int]:
@ -146,3 +146,18 @@ def dip_cut_offs(gains: List[float], peak_gain: float,
attn: float = 3.0) -> Tuple[int, int]:
rng = np.where(np.array(gains) < (peak_gain - attn))[0].tolist()
return (rng[0], rng[-1]) if rng else (math.nan, math.nan)
def calculate_rolloff(s21: List[Datapoint],
idx_1: int, idx_2: int) -> Tuple[float, float]:
if idx_1 == idx_2:
return (math.nan, math.nan)
freq_1 = s21[idx_1].freq
freq_2 = s21[idx_2].freq
gain1 = s21[idx_1].gain
gain2 = s21[idx_2].gain
factor = freq_1 / freq_2 if freq_1 > freq_2 else freq_2 / freq_1
attn = abs(gain1 - gain2)
octave_attn = attn / (math.log10(factor) / math.log10(2))
decade_attn = attn / math.log10(factor)
return (octave_attn, decade_attn)

Wyświetl plik

@ -433,7 +433,6 @@ class FrequencyChart(Chart):
m = self.getActiveMarker()
if m is not None:
m.setFrequency(str(f))
m.frequencyInput.setText(str(f))
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
self.dim.width = (

Wyświetl plik

@ -141,7 +141,6 @@ class SquareChart(Chart):
minimum_position = positions.index(min(positions))
if m := self.getActiveMarker():
m.setFrequency(str(round(target[minimum_position].freq)))
m.frequencyInput.setText(str(round(target[minimum_position].freq)))
def getXPosition(self, d: Datapoint) -> int:
return int(self.width() / 2 + d.re * self.dim.width / 2)

Wyświetl plik

@ -212,6 +212,7 @@ class Marker(QtCore.QObject, Value):
def setFrequency(self, frequency):
self.freq = parse_frequency(frequency)
self.frequencyInput.setText(frequency)
self.updated.emit(self)
def setFieldSelection(self, fields):