1# NanoVNASaver 2# 3# A python program to view and export Touchstone data from a NanoVNA 4# Copyright (C) 2019, 2020 Rune B. Broberg 5# Copyright (C) 2020 NanoVNA-Saver Authors 6# 7# This program is free software: you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program. If not, see <https://www.gnu.org/licenses/>. 19import logging 20import math 21 22from PyQt5 import QtWidgets 23 24from NanoVNASaver.Analysis import Analysis 25from NanoVNASaver.Formatting import format_frequency 26 27logger = logging.getLogger(__name__) 28 29 30class HighPassAnalysis(Analysis): 31 def __init__(self, app): 32 super().__init__(app) 33 34 self._widget = QtWidgets.QWidget() 35 36 layout = QtWidgets.QFormLayout() 37 self._widget.setLayout(layout) 38 layout.addRow(QtWidgets.QLabel("High pass filter analysis")) 39 layout.addRow(QtWidgets.QLabel( 40 f"Please place {self.app.markers[0].name} in the filter passband.")) 41 self.result_label = QtWidgets.QLabel() 42 self.cutoff_label = QtWidgets.QLabel() 43 self.six_db_label = QtWidgets.QLabel() 44 self.sixty_db_label = QtWidgets.QLabel() 45 self.db_per_octave_label = QtWidgets.QLabel() 46 self.db_per_decade_label = QtWidgets.QLabel() 47 layout.addRow("Result:", self.result_label) 48 layout.addRow("Cutoff frequency:", self.cutoff_label) 49 layout.addRow("-6 dB point:", self.six_db_label) 50 layout.addRow("-60 dB point:", self.sixty_db_label) 51 layout.addRow("Roll-off:", self.db_per_octave_label) 52 layout.addRow("Roll-off:", self.db_per_decade_label) 53 54 def reset(self): 55 self.result_label.clear() 56 self.cutoff_label.clear() 57 self.six_db_label.clear() 58 self.sixty_db_label.clear() 59 self.db_per_octave_label.clear() 60 self.db_per_decade_label.clear() 61 62 def runAnalysis(self): 63 self.reset() 64 pass_band_location = self.app.markers[0].location 65 logger.debug("Pass band location: %d", pass_band_location) 66 67 if len(self.app.data21) == 0: 68 logger.debug("No data to analyse") 69 self.result_label.setText("No data to analyse.") 70 return 71 72 if pass_band_location < 0: 73 logger.debug("No location for %s", self.app.markers[0].name) 74 self.result_label.setText( 75 f"Please place {self.app.markers[0].name } in the passband.") 76 return 77 78 pass_band_db = self.app.data21[pass_band_location].gain 79 80 logger.debug("Initial passband gain: %d", pass_band_db) 81 82 initial_cutoff_location = -1 83 for i in range(pass_band_location, -1, -1): 84 db = self.app.data21[i].gain 85 if (pass_band_db - db) > 3: 86 # We found a cutoff location 87 initial_cutoff_location = i 88 break 89 90 if initial_cutoff_location < 0: 91 self.result_label.setText("Cutoff location not found.") 92 return 93 94 initial_cutoff_frequency = self.app.data21[initial_cutoff_location].freq 95 96 logger.debug("Found initial cutoff frequency at %d", initial_cutoff_frequency) 97 98 peak_location = -1 99 peak_db = self.app.data21[initial_cutoff_location].gain 100 for i in range(len(self.app.data21) - 1, initial_cutoff_location - 1, -1): 101 if self.app.data21[i].gain > peak_db: 102 peak_db = db 103 peak_location = i 104 105 logger.debug("Found peak of %f at %d", peak_db, self.app.data11[peak_location].freq) 106 107 self.app.markers[0].setFrequency(str(self.app.data21[peak_location].freq)) 108 self.app.markers[0].frequencyInput.setText(str(self.app.data21[peak_location].freq)) 109 110 cutoff_location = -1 111 pass_band_db = peak_db 112 for i in range(peak_location, -1, -1): 113 if (pass_band_db - self.app.data21[i].gain) > 3: 114 # We found the cutoff location 115 cutoff_location = i 116 break 117 118 cutoff_frequency = self.app.data21[cutoff_location].freq 119 cutoff_gain = self.app.data21[cutoff_location].gain - pass_band_db 120 if cutoff_gain < -4: 121 logger.debug("Cutoff frequency found at %f dB" 122 " - insufficient data points for true -3 dB point.", 123 cutoff_gain) 124 logger.debug("Found true cutoff frequency at %d", cutoff_frequency) 125 126 self.cutoff_label.setText( 127 f"{format_frequency(cutoff_frequency)}" 128 f" {round(cutoff_gain, 1)} dB)") 129 self.app.markers[1].setFrequency(str(cutoff_frequency)) 130 self.app.markers[1].frequencyInput.setText(str(cutoff_frequency)) 131 132 six_db_location = -1 133 for i in range(cutoff_location, -1, -1): 134 if (pass_band_db - self.app.data21[i].gain) > 6: 135 # We found 6dB location 136 six_db_location = i 137 break 138 139 if six_db_location < 0: 140 self.result_label.setText("6 dB location not found.") 141 return 142 six_db_cutoff_frequency = self.app.data21[six_db_location].freq 143 self.six_db_label.setText( 144 format_frequency(six_db_cutoff_frequency)) 145 146 ten_db_location = -1 147 for i in range(cutoff_location, -1, -1): 148 if (pass_band_db - self.app.data21[i].gain) > 10: 149 # We found 6dB location 150 ten_db_location = i 151 break 152 153 twenty_db_location = -1 154 for i in range(cutoff_location, -1, -1): 155 if (pass_band_db - self.app.data21[i].gain) > 20: 156 # We found 6dB location 157 twenty_db_location = i 158 break 159 160 sixty_db_location = -1 161 for i in range(six_db_location, -1, -1): 162 if (pass_band_db - self.app.data21[i].gain) > 60: 163 # We found 60dB location! Wow. 164 sixty_db_location = i 165 break 166 167 if sixty_db_location > 0: 168 if sixty_db_location > 0: 169 sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq 170 self.sixty_db_label.setText( 171 format_frequency(sixty_db_cutoff_frequency)) 172 elif ten_db_location != -1 and twenty_db_location != -1: 173 ten = self.app.data21[ten_db_location].freq 174 twenty = self.app.data21[twenty_db_location].freq 175 sixty_db_frequency = ten * 10 ** (5 * (math.log10(twenty) - math.log10(ten))) 176 self.sixty_db_label.setText( 177 f"{format_frequency(sixty_db_frequency)} (derived)") 178 else: 179 self.sixty_db_label.setText("Not calculated") 180 181 if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: 182 octave_attenuation, decade_attenuation = self.calculateRolloff( 183 ten_db_location, twenty_db_location) 184 self.db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") 185 self.db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") 186 else: 187 self.db_per_octave_label.setText("Not calculated") 188 self.db_per_decade_label.setText("Not calculated") 189 190 self.result_label.setText(f"Analysis complete ({len(self.app.data11)}) points)") 191