nanovna-saver/src/NanoVNASaver/Analysis/HighPassAnalysis.py

122 wiersze
4.5 KiB
Python

# 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
#
# 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# 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
import NanoVNASaver.AnalyticTools as at
from NanoVNASaver.Analysis.Base import Analysis, CUTOFF_VALS
from NanoVNASaver.Formatting import format_frequency
logger = logging.getLogger(__name__)
class HighPassAnalysis(Analysis):
def __init__(self, app):
super().__init__(app)
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 = self.layout
layout.addRow(self.label["titel"])
layout.addRow(QtWidgets.QLabel(
f"Please place {self.app.markers[0].name}"
f" in the filter passband."))
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"])
self.set_titel('Highpass analysis')
def runAnalysis(self):
if not self.app.data.s21:
logger.debug("No data to analyse")
self.set_result("No data to analyse.")
return
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)
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)
octave, decade = at.calculate_rolloff(
s21, cutoff_pos["10.0dB"], cutoff_pos["20.0dB"])
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_freq['3.0dB'])
for label, val in cutoff_freq.items():
self.label[label].setText(
f"{format_frequency(val)}"
f" ({cutoff_gain[label]:.1f} dB)")
self.label['octave'].setText(f'{octave:.3f}dB/octave')
self.label['decade'].setText(f'{decade:.3f}dB/decade')
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']))
self.set_result(f"Analysis complete ({len(s21)}) points)")
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)
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
}