diff --git a/nanovna-saver.py b/nanovna-saver.py index 9204e91..eabbcf3 100755 --- a/nanovna-saver.py +++ b/nanovna-saver.py @@ -28,9 +28,10 @@ try: from NanoVNASaver.__main__ import main except ModuleNotFoundError: import sys - sys.path.append('src') + + sys.path.append("src") from NanoVNASaver.__main__ import main -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/src/NanoVNASaver/About.py b/src/NanoVNASaver/About.py index 5b864fe..b98e5c6 100644 --- a/src/NanoVNASaver/About.py +++ b/src/NanoVNASaver/About.py @@ -20,7 +20,8 @@ VERSION = "0.6.0-pre" VERSION_URL = ( "https://raw.githubusercontent.com/" - "NanoVNA-Saver/nanovna-saver/master/NanoVNASaver/About.py") + "NanoVNA-Saver/nanovna-saver/master/NanoVNASaver/About.py" +) INFO_URL = "https://github.com/NanoVNA-Saver/nanovna-saver" INFO = f"""NanoVNASaver {VERSION} diff --git a/src/NanoVNASaver/Analysis/AntennaAnalysis.py b/src/NanoVNASaver/Analysis/AntennaAnalysis.py index 0be936e..248f114 100644 --- a/src/NanoVNASaver/Analysis/AntennaAnalysis.py +++ b/src/NanoVNASaver/Analysis/AntennaAnalysis.py @@ -35,6 +35,7 @@ class MagLoopAnalysis(VSWRAnalysis): Useful for tuning magloop. """ + max_dips_shown = 1 vswr_bandwith_value = 2.56 # -3 dB ?!? @@ -56,12 +57,17 @@ class MagLoopAnalysis(VSWRAnalysis): if self.min_freq is None: self.min_freq = new_start self.max_freq = new_end - logger.debug("setting hard limits to %s - %s", - self.min_freq, self.max_freq) + logger.debug( + "setting hard limits to %s - %s", self.min_freq, self.max_freq + ) if len(self.minimums) > 1: - self.layout.addRow("", QtWidgets.QLabel( - "Multiple minimums, not magloop or try to lower VSWR limit")) + self.layout.addRow( + "", + QtWidgets.QLabel( + "Multiple minimums, not magloop or try to lower VSWR limit" + ), + ) return if len(self.minimums) == 1: @@ -73,22 +79,25 @@ class MagLoopAnalysis(VSWRAnalysis): logger.debug(" Zoom to %s-%s", new_start, new_end) elif self.vswr_limit_value == self.vswr_bandwith_value: - Q = self.app.data.s11[lowest].freq / \ - (self.app.data.s11[end].freq - - self.app.data.s11[start].freq) + Q = self.app.data.s11[lowest].freq / ( + self.app.data.s11[end].freq - self.app.data.s11[start].freq + ) self.layout.addRow("Q", QtWidgets.QLabel(f"{int(Q)}")) new_start = self.app.data.s11[start].freq - self.bandwith new_end = self.app.data.s11[end].freq + self.bandwith - logger.debug("Single Spot, new scan on %s-%s", - new_start, new_end) + logger.debug( + "Single Spot, new scan on %s-%s", new_start, new_end + ) if self.vswr_limit_value > self.vswr_bandwith_value: self.vswr_limit_value = max( - self.vswr_bandwith_value, self.vswr_limit_value - 1) + self.vswr_bandwith_value, self.vswr_limit_value - 1 + ) self.input_vswr_limit.setValue(self.vswr_limit_value) logger.debug( "found higher minimum, lowering vswr search to %s", - self.vswr_limit_value) + self.vswr_limit_value, + ) else: new_start = new_start - 5 * self.bandwith new_end = new_end + 5 * self.bandwith @@ -100,14 +109,17 @@ class MagLoopAnalysis(VSWRAnalysis): self.input_vswr_limit.setValue(self.vswr_limit_value) logger.debug( "no minimum found, looking for higher value %s", - self.vswr_limit_value) + 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", - new_start, - new_end, - self.vswr_limit_value) + logger.debug( + "next search will be %s - %s for vswr %s", + new_start, + new_end, + self.vswr_limit_value, + ) self.app.sweep_control.set_start(new_start) self.app.sweep_control.set_end(new_end) diff --git a/src/NanoVNASaver/Analysis/BandPassAnalysis.py b/src/NanoVNASaver/Analysis/BandPassAnalysis.py index 45cbc63..461f8ef 100644 --- a/src/NanoVNASaver/Analysis/BandPassAnalysis.py +++ b/src/NanoVNASaver/Analysis/BandPassAnalysis.py @@ -33,42 +33,52 @@ class BandPassAnalysis(Analysis): def __init__(self, app): super().__init__(app) - for label in ('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 = self.layout - layout.addRow(self.label['titel']) + 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']) + f" in the filter passband." + ) + ) + layout.addRow("Result:", self.label["result"]) layout.addRow(QtWidgets.QLabel("")) - layout.addRow("Center frequency:", self.label['freq_center']) - layout.addRow("Bandwidth (-3 dB):", self.label['span_3.0dB']) - layout.addRow("Quality factor:", self.label['q_factor']) - layout.addRow("Bandwidth (-6 dB):", self.label['span_6.0dB']) + layout.addRow("Center frequency:", self.label["freq_center"]) + layout.addRow("Bandwidth (-3 dB):", self.label["span_3.0dB"]) + layout.addRow("Quality factor:", self.label["q_factor"]) + layout.addRow("Bandwidth (-6 dB):", self.label["span_6.0dB"]) layout.addRow(QtWidgets.QLabel("")) layout.addRow(QtWidgets.QLabel("Lower side:")) - layout.addRow("Cutoff frequency:", self.label['3.0dB_l']) - layout.addRow("-6 dB point:", self.label['6.0dB_l']) - layout.addRow("-60 dB point:", self.label['60.0dB_l']) - layout.addRow("Roll-off:", self.label['octave_l']) - layout.addRow("Roll-off:", self.label['decade_l']) + layout.addRow("Cutoff frequency:", self.label["3.0dB_l"]) + layout.addRow("-6 dB point:", self.label["6.0dB_l"]) + layout.addRow("-60 dB point:", self.label["60.0dB_l"]) + layout.addRow("Roll-off:", self.label["octave_l"]) + layout.addRow("Roll-off:", self.label["decade_l"]) layout.addRow(QtWidgets.QLabel("")) layout.addRow(QtWidgets.QLabel("Upper side:")) - layout.addRow("Cutoff frequency:", self.label['3.0dB_r']) - layout.addRow("-6 dB point:", self.label['6.0dB_r']) - layout.addRow("-60 dB point:", self.label['60.0dB_r']) - layout.addRow("Roll-off:", self.label['octave_r']) - layout.addRow("Roll-off:", self.label['decade_r']) + layout.addRow("Cutoff frequency:", self.label["3.0dB_r"]) + layout.addRow("-6 dB point:", self.label["6.0dB_r"]) + layout.addRow("-60 dB point:", self.label["60.0dB_r"]) + layout.addRow("Roll-off:", self.label["octave_r"]) + layout.addRow("Roll-off:", self.label["decade_r"]) self.set_titel("Band pass filter analysis") @@ -103,72 +113,90 @@ class BandPassAnalysis(Analysis): self.derive_60dB(cutoff_pos, cutoff_freq) result = { - 'span_3.0dB': cutoff_freq['3.0dB_r'] - cutoff_freq['3.0dB_l'], - 'span_6.0dB': cutoff_freq['6.0dB_r'] - cutoff_freq['6.0dB_l'], - 'freq_center': - math.sqrt(cutoff_freq['3.0dB_l'] * cutoff_freq['3.0dB_r']), + "span_3.0dB": cutoff_freq["3.0dB_r"] - cutoff_freq["3.0dB_l"], + "span_6.0dB": cutoff_freq["6.0dB_r"] - cutoff_freq["6.0dB_l"], + "freq_center": math.sqrt( + cutoff_freq["3.0dB_l"] * cutoff_freq["3.0dB_r"] + ), } - result['q_factor'] = result['freq_center'] / result['span_3.0dB'] + result["q_factor"] = result["freq_center"] / result["span_3.0dB"] - 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"]) + 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( - f"{format_frequency(val)}" - f" ({cutoff_gain[label]:.1f} dB)") - for label in ('freq_center', 'span_3.0dB', 'span_6.0dB'): + f"{format_frequency(val)}" f" ({cutoff_gain[label]:.1f} dB)" + ) + for label in ("freq_center", "span_3.0dB", "span_6.0dB"): self.label[label].setText(format_frequency(result[label])) - self.label['q_factor'].setText(f"{result['q_factor']:.2f}") + self.label["q_factor"].setText(f"{result['q_factor']:.2f}") - for label in ('octave_l', 'decade_l', 'octave_r', 'decade_r'): + for label in ("octave_l", "decade_l", "octave_r", "decade_r"): self.label[label].setText(f"{result[label]:.3f}dB/{label[:-2]}") self.app.markers[0].setFrequency(f"{result['freq_center']}") self.app.markers[1].setFrequency(f"{cutoff_freq['3.0dB_l']}") self.app.markers[2].setFrequency(f"{cutoff_freq['3.0dB_r']}") - if cutoff_gain['3.0dB_l'] < -4 or cutoff_gain['3.0dB_r'] < -4: + 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']) + "Cutoff gains: %fdB, %fdB", + cutoff_gain["3.0dB_l"], + cutoff_gain["3.0dB_r"], + ) self.set_result( f"Analysis complete ({len(s21)} points)\n" - f"Insufficient data for analysis. Increase segment count.") + f"Insufficient data for analysis. Increase segment count." + ) return self.set_result(f"Analysis complete ({len(s21)} points)") - def derive_60dB(self, - cutoff_pos: Dict[str, int], - cutoff_freq: Dict[str, float]): + def derive_60dB( + self, cutoff_pos: Dict[str, int], cutoff_freq: Dict[str, float] + ): """derive 60dB cutoff if needed an possible Args: cutoff_pos (Dict[str, int]) cutoff_freq (Dict[str, float]) """ - if (math.isnan(cutoff_freq['60.0dB_l']) and - cutoff_pos['20.0dB_l'] != -1 and cutoff_pos['10.0dB_l'] != -1): - cutoff_freq['60.0dB_l'] = ( - cutoff_freq["10.0dB_l"] * - 10 ** (5 * (math.log10(cutoff_pos['20.0dB_l']) - - math.log10(cutoff_pos['10.0dB_l'])))) - if (math.isnan(cutoff_freq['60.0dB_r']) and - cutoff_pos['20.0dB_r'] != -1 and cutoff_pos['10.0dB_r'] != -1): - cutoff_freq['60.0dB_r'] = ( - cutoff_freq["10.0dB_r"] * - 10 ** (5 * (math.log10(cutoff_pos['20.0dB_r']) - - math.log10(cutoff_pos['10.0dB_r']) - ))) + if ( + math.isnan(cutoff_freq["60.0dB_l"]) + and cutoff_pos["20.0dB_l"] != -1 + and cutoff_pos["10.0dB_l"] != -1 + ): + cutoff_freq["60.0dB_l"] = cutoff_freq["10.0dB_l"] * 10 ** ( + 5 + * ( + math.log10(cutoff_pos["20.0dB_l"]) + - math.log10(cutoff_pos["10.0dB_l"]) + ) + ) + if ( + math.isnan(cutoff_freq["60.0dB_r"]) + and cutoff_pos["20.0dB_r"] != -1 + and cutoff_pos["10.0dB_r"] != -1 + ): + cutoff_freq["60.0dB_r"] = cutoff_freq["10.0dB_r"] * 10 ** ( + 5 + * ( + math.log10(cutoff_pos["20.0dB_r"]) + - math.log10(cutoff_pos["10.0dB_r"]) + ) + ) def find_center(self, gains: List[float]) -> int: marker = self.app.markers[0] if marker.location <= 0 or marker.location >= len(gains) - 1: - logger.debug("No valid location for %s (%s)", - marker.name, marker.location) + logger.debug( + "No valid location for %s (%s)", marker.name, marker.location + ) self.set_result(f"Please place {marker.name} in the passband.") return -1 @@ -178,13 +206,15 @@ class BandPassAnalysis(Analysis): return -1 return peak - def find_bounderies(self, - gains: List[float], - peak: int, peak_db: float) -> Dict[str, int]: + def find_bounderies( + self, gains: List[float], peak: int, peak_db: float + ) -> Dict[str, int]: cutoff_pos = {} for attn in CUTOFF_VALS: cutoff_pos[f"{attn:.1f}dB_l"] = at.cut_off_left( - gains, peak, peak_db, attn) + gains, peak, peak_db, attn + ) cutoff_pos[f"{attn:.1f}dB_r"] = at.cut_off_right( - gains, peak, peak_db, attn) + gains, peak, peak_db, attn + ) return cutoff_pos diff --git a/src/NanoVNASaver/Analysis/BandStopAnalysis.py b/src/NanoVNASaver/Analysis/BandStopAnalysis.py index fd3a982..da4395f 100644 --- a/src/NanoVNASaver/Analysis/BandStopAnalysis.py +++ b/src/NanoVNASaver/Analysis/BandStopAnalysis.py @@ -34,11 +34,13 @@ class BandStopAnalysis(BandPassAnalysis): def find_center(self, gains: List[float]) -> int: return max(enumerate(gains), key=lambda i: i[1])[0] - def find_bounderies(self, - gains: List[float], - _: int, peak_db: float) -> Dict[str, int]: + def find_bounderies( + self, gains: List[float], _: int, peak_db: float + ) -> Dict[str, int]: cutoff_pos = {} for attn in CUTOFF_VALS: - cutoff_pos[f"{attn:.1f}dB_l"], cutoff_pos[f"{attn:.1f}dB_r"] = ( - at.dip_cut_offs(gains, peak_db, attn)) + ( + cutoff_pos[f"{attn:.1f}dB_l"], + cutoff_pos[f"{attn:.1f}dB_r"], + ) = at.dip_cut_offs(gains, peak_db, attn) return cutoff_pos diff --git a/src/NanoVNASaver/Analysis/Base.py b/src/NanoVNASaver/Analysis/Base.py index 0f4e6a9..a47cae2 100644 --- a/src/NanoVNASaver/Analysis/Base.py +++ b/src/NanoVNASaver/Analysis/Base.py @@ -35,8 +35,8 @@ class Analysis: def __init__(self, app: QtWidgets.QWidget): self.app = app self.label: Dict[str, QtWidgets.QLabel] = { - 'titel': QtWidgets.QLabel(), - 'result': QtWidgets.QLabel(), + "titel": QtWidgets.QLabel(), + "result": QtWidgets.QLabel(), } self.layout = QtWidgets.QFormLayout() self._widget = QtWidgets.QWidget() @@ -53,7 +53,7 @@ class Analysis: label.clear() def set_result(self, text): - self.label['result'].setText(text) + self.label["result"].setText(text) def set_titel(self, text): - self.label['titel'].setText(text) + self.label["titel"].setText(text) diff --git a/src/NanoVNASaver/Analysis/EFHWAnalysis.py b/src/NanoVNASaver/Analysis/EFHWAnalysis.py index 2eca1b9..37d7517 100644 --- a/src/NanoVNASaver/Analysis/EFHWAnalysis.py +++ b/src/NanoVNASaver/Analysis/EFHWAnalysis.py @@ -23,10 +23,14 @@ from PyQt5 import QtWidgets import NanoVNASaver.AnalyticTools as at from NanoVNASaver.Analysis.ResonanceAnalysis import ( - ResonanceAnalysis, format_resistence_neg + ResonanceAnalysis, + format_resistence_neg, ) from NanoVNASaver.Formatting import ( - format_frequency, format_complex_imp, format_frequency_short) + format_frequency, + format_complex_imp, + format_frequency_short, +) logger = logging.getLogger(__name__) @@ -43,8 +47,8 @@ class EFHWAnalysis(ResonanceAnalysis): def do_resonance_analysis(self): s11 = self.app.data.s11 maximums = sorted( - at.maxima([d.impedance().real for d in s11], - threshold=500)) + at.maxima([d.impedance().real for d in s11], threshold=500) + ) extended_data = {} logger.info("TO DO: find near data") for lowest in self.crossings: @@ -61,12 +65,14 @@ class EFHWAnalysis(ResonanceAnalysis): 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))] + fields = [ + ("freq", format_frequency_short), + ("r", format_resistence_neg), + ("lambda", lambda x: round(x, 2)), + ] if self.old_data: - diff = self.compare( - self.old_data[-1], extended_data, fields=fields) + 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) @@ -76,14 +82,17 @@ class EFHWAnalysis(ResonanceAnalysis): QtWidgets.QLabel( f" ({diff[i]['freq']})" f" {format_complex_imp(s11[idx].impedance())}" - f" ({diff[i]['r']}) {diff[i]['lambda']} m")) + f" ({diff[i]['r']}) {diff[i]['lambda']} m" + ), + ) if self.filename and extended_data: with open( - self.filename, 'w', newline='', encoding='utf-8' + self.filename, "w", newline="", encoding="utf-8" ) as csvfile: - fieldnames = extended_data[sorted( - extended_data.keys())[0]].keys() + fieldnames = extended_data[ + sorted(extended_data.keys())[0] + ].keys() writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() for idx in sorted(extended_data.keys()): @@ -99,10 +108,11 @@ class EFHWAnalysis(ResonanceAnalysis): :param old: :param new: """ - fields = fields or [("freq", str), ] + fields = fields or [ + ("freq", str), + ] def no_compare(): - return {k: "-" for k, _ in fields} old_idx = sorted(old.keys()) @@ -113,8 +123,9 @@ class EFHWAnalysis(ResonanceAnalysis): i_tot = max(len(old_idx), len(new_idx)) if i_max != i_tot: - logger.warning("resonances changed from %s to %s", - len(old_idx), len(new_idx)) + logger.warning( + "resonances changed from %s to %s", len(old_idx), len(new_idx) + ) split = 0 max_delta_f = 1_000_000 @@ -135,15 +146,19 @@ class EFHWAnalysis(ResonanceAnalysis): logger.debug("Deltas %s", diff[i]) continue - logger.debug("can't compare, %s is too much ", - format_frequency(delta_f)) + 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): + 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 ?!? diff --git a/src/NanoVNASaver/Analysis/HighPassAnalysis.py b/src/NanoVNASaver/Analysis/HighPassAnalysis.py index a8aeba5..fd4419f 100644 --- a/src/NanoVNASaver/Analysis/HighPassAnalysis.py +++ b/src/NanoVNASaver/Analysis/HighPassAnalysis.py @@ -41,9 +41,12 @@ class HighPassAnalysis(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.")) + 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"]) @@ -51,7 +54,7 @@ class HighPassAnalysis(Analysis): layout.addRow("Roll-off:", self.label["octave"]) layout.addRow("Roll-off:", self.label["decade"]) - self.set_titel('Highpass analysis') + self.set_titel("Highpass analysis") def runAnalysis(self): if not self.app.data.s21: @@ -81,25 +84,28 @@ class HighPassAnalysis(Analysis): logger.debug("Cuttoff gains: %s", cutoff_gain) octave, decade = at.calculate_rolloff( - s21, cutoff_pos["10.0dB"], cutoff_pos["20.0dB"]) + 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']) + 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)") + 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.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.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)") @@ -111,11 +117,10 @@ class HighPassAnalysis(Analysis): 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]: + 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) + f"{attn:.1f}dB": at.cut_off_left(gains, peak, peak_db, attn) for attn in CUTOFF_VALS } diff --git a/src/NanoVNASaver/Analysis/LowPassAnalysis.py b/src/NanoVNASaver/Analysis/LowPassAnalysis.py index 9516bb9..764e791 100644 --- a/src/NanoVNASaver/Analysis/LowPassAnalysis.py +++ b/src/NanoVNASaver/Analysis/LowPassAnalysis.py @@ -30,13 +30,12 @@ class LowPassAnalysis(HighPassAnalysis): def __init__(self, app): super().__init__(app) - self.set_titel('Lowpass filter analysis') + self.set_titel("Lowpass filter analysis") - def find_cutoffs(self, - gains: List[float], - peak: int, peak_db: float) -> Dict[str, int]: + 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) + f"{attn:.1f}dB": at.cut_off_right(gains, peak, peak_db, attn) for attn in CUTOFF_VALS } diff --git a/src/NanoVNASaver/Analysis/PeakSearchAnalysis.py b/src/NanoVNASaver/Analysis/PeakSearchAnalysis.py index 65c5742..815d988 100644 --- a/src/NanoVNASaver/Analysis/PeakSearchAnalysis.py +++ b/src/NanoVNASaver/Analysis/PeakSearchAnalysis.py @@ -20,12 +20,14 @@ import logging from PyQt5 import QtWidgets import numpy as np + # pylint: disable=import-error, no-name-in-module from scipy.signal import find_peaks, peak_prominences from NanoVNASaver.Analysis.Base import QHLine from NanoVNASaver.Analysis.SimplePeakSearchAnalysis import ( - SimplePeakSearchAnalysis) + SimplePeakSearchAnalysis, +) from NanoVNASaver.Formatting import format_frequency_short @@ -34,7 +36,6 @@ logger = logging.getLogger(__name__) class PeakSearchAnalysis(SimplePeakSearchAnalysis): - def __init__(self, app): super().__init__(app) @@ -48,7 +49,7 @@ class PeakSearchAnalysis(SimplePeakSearchAnalysis): self.layout.addRow(QtWidgets.QLabel("Results")) self.results_header = self.layout.rowCount() - self.set_titel('Peak search') + self.set_titel("Peak search") def runAnalysis(self): if not self.app.data.s11: @@ -59,14 +60,14 @@ class PeakSearchAnalysis(SimplePeakSearchAnalysis): data, fmt_fnc = self.data_and_format() inverted = False - if self.button['peak_l'].isChecked(): + if self.button["peak_l"].isChecked(): inverted = True peaks, _ = find_peaks( - -np.array(data), width=3, distance=3, prominence=1) + -np.array(data), width=3, distance=3, prominence=1 + ) else: - self.button['peak_h'].setChecked(True) - peaks, _ = find_peaks( - data, width=3, distance=3, prominence=1) + self.button["peak_h"].setChecked(True) + peaks, _ = find_peaks(data, width=3, distance=3, prominence=1) # Having found the peaks, get the prominence data for i, p in np.ndenumerate(peaks): @@ -89,19 +90,24 @@ class PeakSearchAnalysis(SimplePeakSearchAnalysis): f"Freq: {format_frequency_short(s11[pos].freq)}", QtWidgets.QLabel( f" Value: {fmt_fnc(-data[pos] if inverted else data[pos])}" - )) + ), + ) - if self.button['move_marker'].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(s11[peaks[indices[i]]].freq)) + str(s11[peaks[indices[i]]].freq) + ) def reset(self): super().reset() - logger.debug("Results start at %d, out of %d", - self.results_header, self.layout.rowCount()) + logger.debug( + "Results start at %d, out of %d", + self.results_header, + self.layout.rowCount(), + ) for _ in range(self.results_header, self.layout.rowCount()): logger.debug("deleting %s", self.layout.rowCount()) self.layout.removeRow(self.layout.rowCount() - 1) diff --git a/src/NanoVNASaver/Analysis/ResonanceAnalysis.py b/src/NanoVNASaver/Analysis/ResonanceAnalysis.py index 00d8dac..b427067 100644 --- a/src/NanoVNASaver/Analysis/ResonanceAnalysis.py +++ b/src/NanoVNASaver/Analysis/ResonanceAnalysis.py @@ -25,9 +25,7 @@ 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, - format_resistance) +from NanoVNASaver.Formatting import format_frequency, format_resistance from NanoVNASaver.RFTools import reflection_coefficient logger = logging.getLogger(__name__) @@ -44,7 +42,6 @@ def vswr_transformed(z, ratio=49) -> float: class ResonanceAnalysis(Analysis): - def __init__(self, app): super().__init__(app) self.crossings: List[int] = [] @@ -72,10 +69,8 @@ class ResonanceAnalysis(Analysis): "impedance": s11[index].impedance(), "vswr": s11[index].vswr, } - my_data["vswr_49"] = vswr_transformed( - my_data["impedance"], 49) - my_data["vswr_4"] = vswr_transformed( - my_data["impedance"], 4) + my_data["vswr_49"] = vswr_transformed(my_data["impedance"], 49) + my_data["vswr_4"] = vswr_transformed(my_data["impedance"], 4) my_data["r"] = my_data["impedance"].real my_data["x"] = my_data["impedance"].imag @@ -83,24 +78,28 @@ class ResonanceAnalysis(Analysis): def runAnalysis(self): self.reset() - self.filename = os.path.join( - "/tmp/", f"{self.input_description.text()}.csv" - ) if self.input_description.text() else "" + self.filename = ( + os.path.join("/tmp/", f"{self.input_description.text()}.csv") + if self.input_description.text() + else "" + ) 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 _ in range(results_header, self.layout.rowCount()): self.layout.removeRow(self.layout.rowCount() - 1) self.crossings = sorted( - set(at.zero_crossings([d.phase for d in self.app.data.s11]))) - logger.debug("Found %d sections ", - len(self.crossings)) + set(at.zero_crossings([d.phase for d in self.app.data.s11])) + ) + logger.debug("Found %d sections ", len(self.crossings)) if not self.crossings: - self.layout.addRow(QtWidgets.QLabel( - "No resonance found")) + self.layout.addRow(QtWidgets.QLabel("No resonance found")) return self @@ -111,14 +110,18 @@ class ResonanceAnalysis(Analysis): extended_data = [] for crossing in self.crossings: extended_data.append(self._get_data(crossing)) - self.layout.addRow("Resonance", QtWidgets.QLabel( - format_frequency(self.app.data.s11[crossing].freq))) + self.layout.addRow( + "Resonance", + QtWidgets.QLabel( + format_frequency(self.app.data.s11[crossing].freq) + ), + ) self.layout.addWidget(QHLine()) # Remove the final separator line self.layout.removeRow(self.layout.rowCount() - 1) if self.filename and extended_data: with open( - self.filename, 'w', encoding='utf-8', newline='' + self.filename, "w", encoding="utf-8", newline="" ) as csvfile: fieldnames = extended_data[0].keys() writer = csv.DictWriter(csvfile, fieldnames=fieldnames) diff --git a/src/NanoVNASaver/Analysis/SimplePeakSearchAnalysis.py b/src/NanoVNASaver/Analysis/SimplePeakSearchAnalysis.py index eabcf4e..91d4cb7 100644 --- a/src/NanoVNASaver/Analysis/SimplePeakSearchAnalysis.py +++ b/src/NanoVNASaver/Analysis/SimplePeakSearchAnalysis.py @@ -24,7 +24,11 @@ import numpy as np from NanoVNASaver.Analysis.Base import Analysis, QHLine from NanoVNASaver.Formatting import ( - format_frequency, format_gain, format_resistance, format_vswr) + format_frequency, + format_gain, + format_resistance, + format_vswr, +) logger = logging.getLogger(__name__) @@ -33,51 +37,51 @@ class SimplePeakSearchAnalysis(Analysis): def __init__(self, app): super().__init__(app) - self.label['peak_freq'] = QtWidgets.QLabel() - self.label['peak_db'] = QtWidgets.QLabel() + self.label["peak_freq"] = QtWidgets.QLabel() + self.label["peak_db"] = QtWidgets.QLabel() 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() + "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.button['gain'].setChecked(True) - self.button['peak_h'].setChecked(True) + self.button["gain"].setChecked(True) + self.button["peak_h"].setChecked(True) self.btn_group = { - 'data': QtWidgets.QButtonGroup(), - 'peak': QtWidgets.QButtonGroup(), + "data": QtWidgets.QButtonGroup(), + "peak": QtWidgets.QButtonGroup(), } - 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']) + 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"]) layout = self.layout - layout.addRow(self.label['titel']) + layout.addRow(self.label["titel"]) layout.addRow(QHLine()) layout.addRow(QtWidgets.QLabel("Settings")) - layout.addRow("Data source", self.button['vswr']) - layout.addRow("", self.button['resistance']) - layout.addRow("", self.button['reactance']) - layout.addRow("", self.button['gain']) + 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("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("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']) + layout.addRow(self.label["result"]) + layout.addRow("Peak frequency:", self.label["peak_freq"]) + layout.addRow("Peak value:", self.label["peak_db"]) - self.set_titel('Simple peak search') + self.set_titel("Simple peak search") def runAnalysis(self): if not self.app.data.s11: @@ -86,16 +90,16 @@ class SimplePeakSearchAnalysis(Analysis): s11 = self.app.data.s11 data, fmt_fnc = self.data_and_format() - if self.button['peak_l'].isChecked(): + if self.button["peak_l"].isChecked(): idx_peak = np.argmin(data) else: - self.button['peak_h'].setChecked(True) + self.button["peak_h"].setChecked(True) idx_peak = np.argmax(data) - self.label['peak_freq'].setText(format_frequency(s11[idx_peak].freq)) - self.label['peak_db'].setText(fmt_fnc(data[idx_peak])) + self.label["peak_freq"].setText(format_frequency(s11[idx_peak].freq)) + self.label["peak_db"].setText(fmt_fnc(data[idx_peak])) - if self.button['move_marker'].isChecked() and self.app.markers: + if self.button["move_marker"].isChecked() and self.app.markers: self.app.markers[0].setFrequency(f"{s11[idx_peak].freq}") def data_and_format(self) -> Tuple[List[float], Callable]: @@ -103,17 +107,17 @@ class SimplePeakSearchAnalysis(Analysis): s21 = self.app.data.s21 if not s21: - self.button['gain'].setEnabled(False) - if self.button['gain'].isChecked(): - self.button['vswr'].setChecked(True) + self.button["gain"].setEnabled(False) + if self.button["gain"].isChecked(): + self.button["vswr"].setChecked(True) else: - self.button['gain'].setEnabled(True) + self.button["gain"].setEnabled(True) - if self.button['gain'].isChecked(): + if self.button["gain"].isChecked(): return ([d.gain for d in s21], format_gain) - if self.button['resistance'].isChecked(): + if self.button["resistance"].isChecked(): return ([d.impedance().real for d in s11], format_resistance) - if self.button['reactance'].isChecked(): + if self.button["reactance"].isChecked(): return ([d.impedance().imag for d in s11], format_resistance) # default return ([d.vswr for d in s11], format_vswr) diff --git a/src/NanoVNASaver/Analysis/VSWRAnalysis.py b/src/NanoVNASaver/Analysis/VSWRAnalysis.py index 9939fce..0081b49 100644 --- a/src/NanoVNASaver/Analysis/VSWRAnalysis.py +++ b/src/NanoVNASaver/Analysis/VSWRAnalysis.py @@ -64,34 +64,50 @@ class VSWRAnalysis(Analysis): data = [d.vswr for d in s11] threshold = self.input_vswr_limit.value() - minima = sorted(at.minima(data, threshold), - key=lambda i: data[i])[:VSWRAnalysis.max_dips_shown] + minima = sorted(at.minima(data, threshold), key=lambda i: data[i])[ + : VSWRAnalysis.max_dips_shown + ] self.minimums = minima 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 _ in range(results_header, self.layout.rowCount()): self.layout.removeRow(self.layout.rowCount() - 1) if not minima: - self.layout.addRow(QtWidgets.QLabel( - f"No areas found with VSWR below {format_vswr(threshold)}.")) + self.layout.addRow( + QtWidgets.QLabel( + f"No areas found with VSWR below {format_vswr(threshold)}." + ) + ) return for idx in minima: rng = at.take_from_idx(data, idx, lambda i: i[1] < threshold) begin, end = rng[0], rng[-1] - self.layout.addRow("Start", QtWidgets.QLabel( - format_frequency(s11[begin].freq))) - self.layout.addRow("Minimum", QtWidgets.QLabel( - f"{format_frequency(s11[idx].freq)}" - f" ({round(s11[idx].vswr, 2)})")) - self.layout.addRow("End", QtWidgets.QLabel( - format_frequency(s11[end].freq))) self.layout.addRow( - "Span", QtWidgets.QLabel(format_frequency( - (s11[end].freq - s11[begin].freq)))) + "Start", QtWidgets.QLabel(format_frequency(s11[begin].freq)) + ) + self.layout.addRow( + "Minimum", + QtWidgets.QLabel( + f"{format_frequency(s11[idx].freq)}" + f" ({round(s11[idx].vswr, 2)})" + ), + ) + self.layout.addRow( + "End", QtWidgets.QLabel(format_frequency(s11[end].freq)) + ) + self.layout.addRow( + "Span", + QtWidgets.QLabel( + format_frequency((s11[end].freq - s11[begin].freq)) + ), + ) self.layout.addWidget(QHLine()) self.layout.removeRow(self.layout.rowCount() - 1) diff --git a/src/NanoVNASaver/AnalyticTools.py b/src/NanoVNASaver/AnalyticTools.py index 9551f72..c68b8b3 100644 --- a/src/NanoVNASaver/AnalyticTools.py +++ b/src/NanoVNASaver/AnalyticTools.py @@ -21,6 +21,7 @@ import math from typing import Callable, List, Tuple import numpy as np + # pylint: disable=import-error, no-name-in-module from scipy.signal import find_peaks @@ -42,8 +43,9 @@ def zero_crossings(data: List[float]) -> List[int]: np_data = np.array(data) # start with real zeros (ignore first and last element) - real_zeros = [n for n in np.where(np_data == 0.0)[0] if - n not in {0, np_data.size - 1}] + real_zeros = [ + n for n in np.where(np_data == 0.0)[0] if n not in {0, np_data.size - 1} + ] # now multipy elements to find change in signess crossings = [ n if abs(np_data[n]) < abs(np_data[n + 1]) else n + 1 @@ -61,11 +63,8 @@ def maxima(data: List[float], threshold: float = 0.0) -> List[int]: Returns: List[int]: indices of maxima """ - peaks = find_peaks( - data, width=2, distance=3, prominence=1)[0].tolist() - return [ - i for i in peaks if data[i] > threshold - ] if threshold else peaks + peaks = find_peaks(data, width=2, distance=3, prominence=1)[0].tolist() + return [i for i in peaks if data[i] > threshold] if threshold else peaks def minima(data: List[float], threshold: float = 0.0) -> List[int]: @@ -77,16 +76,15 @@ def minima(data: List[float], threshold: float = 0.0) -> List[int]: Returns: List[int]: indices of minima """ - bottoms = find_peaks( - -np.array(data), width=2, distance=3, prominence=1)[0].tolist() - return [ - i for i in bottoms if data[i] < threshold - ] if threshold else bottoms + bottoms = find_peaks(-np.array(data), width=2, distance=3, prominence=1)[ + 0 + ].tolist() + return [i for i in bottoms if data[i] < threshold] if threshold else bottoms -def take_from_idx(data: List[float], - idx: int, - predicate: Callable) -> List[int]: +def take_from_idx( + data: List[float], idx: int, predicate: Callable +) -> List[int]: """take_from_center Args: @@ -99,18 +97,21 @@ def take_from_idx(data: List[float], List[int]: indices of element matching predicate left and right from index """ - lower = list(reversed( - [i for i, _ in - it.takewhile(predicate, - reversed(list(enumerate(data[:idx]))))])) - upper = [i for i, _ in - it.takewhile(predicate, - enumerate(data[idx:], idx))] + lower = list( + reversed( + [ + i + for i, _ in it.takewhile( + predicate, reversed(list(enumerate(data[:idx]))) + ) + ] + ) + ) + upper = [i for i, _ in it.takewhile(predicate, enumerate(data[idx:], idx))] return lower + upper -def center_from_idx(gains: List[float], - idx: int, delta: float = 3.0) -> int: +def center_from_idx(gains: List[float], idx: int, delta: float = 3.0) -> int: """find maximum from index postion of gains in a attn dB gain span Args: @@ -122,13 +123,13 @@ def center_from_idx(gains: List[float], int: position of highest gain from start in range (-1 if no data) """ peak_db = gains[idx] - rng = take_from_idx(gains, idx, - lambda i: abs(peak_db - i[1]) < delta) + rng = take_from_idx(gains, idx, lambda i: abs(peak_db - i[1]) < delta) return max(rng, key=lambda i: gains[i]) if rng else -1 -def cut_off_left(gains: List[float], idx: int, - peak_gain: float, attn: float = 3.0) -> int: +def cut_off_left( + gains: List[float], idx: int, peak_gain: float, attn: float = 3.0 +) -> int: """find first position in list where gain in attn lower then peak left from index @@ -143,13 +144,13 @@ def cut_off_left(gains: List[float], idx: int, int: position of attenuation point. (-1 if no data) """ return next( - (i for i in range(idx, -1, -1) if - (peak_gain - gains[i]) > attn), - -1) + (i for i in range(idx, -1, -1) if (peak_gain - gains[i]) > attn), -1 + ) -def cut_off_right(gains: List[float], idx: int, - peak_gain: float, attn: float = 3.0) -> int: +def cut_off_right( + gains: List[float], idx: int, peak_gain: float, attn: float = 3.0 +) -> int: """find first position in list where gain in attn lower then peak right from index @@ -165,19 +166,20 @@ def cut_off_right(gains: List[float], idx: int, """ return next( - (i for i in range(idx, len(gains)) if - (peak_gain - gains[i]) > attn), - -1) + (i for i in range(idx, len(gains)) if (peak_gain - gains[i]) > attn), -1 + ) -def dip_cut_offs(gains: List[float], peak_gain: float, - attn: float = 3.0) -> Tuple[int, int]: +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]: +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, freq_2 = s21[idx_1].freq, s21[idx_2].freq diff --git a/src/NanoVNASaver/Calibration.py b/src/NanoVNASaver/Calibration.py index ec93d32..2ab6979 100644 --- a/src/NanoVNASaver/Calibration.py +++ b/src/NanoVNASaver/Calibration.py @@ -35,7 +35,8 @@ IDEAL_OPEN = complex(1, 0) IDEAL_LOAD = complex(0, 0) IDEAL_THROUGH = complex(1, 0) -RXP_CAL_HEADER = re.compile(r""" +RXP_CAL_HEADER = re.compile( + r""" ^ \# \s+ Hz \s+ ShortR \s+ ShortI \s+ OpenR \s+ OpenI \s+ LoadR \s+ LoadI @@ -43,9 +44,12 @@ RXP_CAL_HEADER = re.compile(r""" (?P \s+ ThrureflR \s+ ThrureflI)? (?P \s+ IsolationR \s+ IsolationI)? \s* $ -""", re.VERBOSE | re.IGNORECASE) +""", + re.VERBOSE | re.IGNORECASE, +) -RXP_CAL_LINE = re.compile(r""" +RXP_CAL_LINE = re.compile( + r""" ^ \s* (?P\d+) \s+ (?P[-0-9Ee.]+) \s+ (?P[-0-9Ee.]+) \s+ @@ -55,7 +59,9 @@ RXP_CAL_LINE = re.compile(r""" ( \s+ (?P[-0-9Ee.]+) \s+ (?P[-0-9Ee.]+))? ( \s+ (?P[-0-9Ee.]+) \s+ (?P[-0-9Ee.]+))? \s* $ -""", re.VERBOSE) +""", + re.VERBOSE, +) logger = logging.getLogger(__name__) @@ -63,7 +69,8 @@ logger = logging.getLogger(__name__) def correct_delay(d: Datapoint, delay: float, reflect: bool = False): mult = 2 if reflect else 1 corr_data = d.z * cmath.exp( - complex(0, 1) * 2 * math.pi * d.freq * delay * -1 * mult) + complex(0, 1) * 2 * math.pi * d.freq * delay * -1 * mult + ) return Datapoint(d.freq, corr_data.real, corr_data.imag) @@ -88,14 +95,16 @@ class CalData: def __str__(self): return ( - f'{self.freq}' - f' {self.short.real} {self.short.imag}' - f' {self.open.real} {self.open.imag}' - f' {self.load.real} {self.load.imag}' + ( - f' {self.through.real} {self.through.imag}' - f' {self.thrurefl.real} {self.thrurefl.imag}' - f' {self.isolation.real} {self.isolation.imag}' - if self.through else '' + f"{self.freq}" + f" {self.short.real} {self.short.imag}" + f" {self.open.real} {self.open.imag}" + f" {self.load.real} {self.load.imag}" + + ( + f" {self.through.real} {self.through.imag}" + f" {self.thrurefl.real} {self.thrurefl.imag}" + f" {self.isolation.real} {self.isolation.imag}" + if self.through + else "" ) ) @@ -138,26 +147,32 @@ class CalDataSet(UserDict): ( "# Calibration data for NanoVNA-Saver\n" + "\n".join([f"! {note}" for note in self.notes.splitlines()]) - + "\n" + "# Hz ShortR ShortI OpenR OpenI LoadR LoadI" - + (" ThroughR ThroughI ThrureflR" - " ThrureflI IsolationR IsolationI\n" - if self.complete2port() else "\n") - + "\n".join([ - f"{self.data.get(freq)}" for freq in self.frequencies() - ]) + "\n" + + "\n" + + "# Hz ShortR ShortI OpenR OpenI LoadR LoadI" + + ( + " ThroughR ThroughI ThrureflR" + " ThrureflI IsolationR IsolationI\n" + if self.complete2port() + else "\n" + ) + + "\n".join( + [f"{self.data.get(freq)}" for freq in self.frequencies()] + ) + + "\n" ) - if self.complete1port() else "" + if self.complete1port() + else "" ) - def _append_match(self, m: re.Match, header: str, - line_nr: int, line: str) -> None: + def _append_match( + self, m: re.Match, header: str, line_nr: int, line: str + ) -> None: cal = m.groupdict() - columns = { - col[:-1] for col in cal.keys() if cal[col] and col != "freq" - } + columns = {col[:-1] for col in cal.keys() if cal[col] and col != "freq"} if "through" in columns and header == "sol": - logger.warning("Through data with sol header. %i: %s", - line_nr, line) + logger.warning( + "Through data with sol header. %i: %s", line_nr, line + ) # fix short data (without thrurefl) if "thrurefl" in columns and "isolation" not in columns: cal["isolationr"] = cal["thrureflr"] @@ -166,11 +181,14 @@ class CalDataSet(UserDict): for name in columns: self.insert( name, - Datapoint(int(cal["freq"]), - float(cal[f"{name}r"]), - float(cal[f"{name}i"]))) + Datapoint( + int(cal["freq"]), + float(cal[f"{name}r"]), + float(cal[f"{name}i"]), + ), + ) - def from_str(self, text: str) -> 'CalDataSet': + def from_str(self, text: str) -> "CalDataSet": # reset data self.notes = "" self.data = defaultdict(CalData) @@ -185,7 +203,8 @@ class CalDataSet(UserDict): if m := RXP_CAL_HEADER.search(line): if header: logger.warning( - "Duplicate header in cal data. %i: %s", i, line) + "Duplicate header in cal data. %i: %s", i, line + ) header = "through" if m.group("through") else "sol" continue if not line or line.startswith("#"): @@ -197,13 +216,20 @@ class CalDataSet(UserDict): continue if not header: logger.warning( - "Caldata without having read header: %i: %s", i, line) + "Caldata without having read header: %i: %s", i, line + ) self._append_match(m, header, line, i) return self def insert(self, name: str, dp: Datapoint): - if name not in {'short', 'open', 'load', - 'through', 'thrurefl', 'isolation'}: + if name not in { + "short", + "open", + "load", + "through", + "thrurefl", + "isolation", + }: raise KeyError(name) freq = dp.freq setattr(self.data[freq], name, (dp.z)) @@ -223,9 +249,7 @@ class CalDataSet(UserDict): yield self.get(freq) def size_of(self, name: str) -> int: - return len( - [True for val in self.data.values() if getattr(val, name)] - ) + return len([True for val in self.data.values() if getattr(val, name)]) def complete1port(self) -> bool: for val in self.data.values(): @@ -244,7 +268,6 @@ class CalDataSet(UserDict): class Calibration: def __init__(self): - self.notes = [] self.dataset = CalDataSet() self.cal_element = CalElement() @@ -278,18 +301,30 @@ class Calibration: gm2 = cal.open gm3 = cal.load - denominator = (g1 * (g2 - g3) * gm1 + - g2 * g3 * gm2 - g2 * g3 * gm3 - - (g2 * gm2 - g3 * gm3) * g1) - cal.e00 = - ((g2 * gm3 - g3 * gm3) * g1 * gm2 - - (g2 * g3 * gm2 - g2 * g3 * gm3 - - (g3 * gm2 - g2 * gm3) * g1) * gm1 - ) / denominator - cal.e11 = ((g2 - g3) * gm1 - g1 * (gm2 - gm3) + - g3 * gm2 - g2 * gm3) / denominator - cal.delta_e = - ((g1 * (gm2 - gm3) - g2 * gm2 + g3 * - gm3) * gm1 + (g2 * gm3 - g3 * gm3) * - gm2) / denominator + denominator = ( + g1 * (g2 - g3) * gm1 + + g2 * g3 * gm2 + - g2 * g3 * gm3 + - (g2 * gm2 - g3 * gm3) * g1 + ) + cal.e00 = ( + -( + (g2 * gm3 - g3 * gm3) * g1 * gm2 + - (g2 * g3 * gm2 - g2 * g3 * gm3 - (g3 * gm2 - g2 * gm3) * g1) + * gm1 + ) + / denominator + ) + cal.e11 = ( + (g2 - g3) * gm1 - g1 * (gm2 - gm3) + g3 * gm2 - g2 * gm3 + ) / denominator + cal.delta_e = ( + -( + (g1 * (gm2 - gm3) - g2 * gm2 + g3 * gm3) * gm1 + + (g2 * gm3 - g3 * gm3) * gm2 + ) + / denominator + ) def _calc_port_2(self, freq: int, cal: CalData): gt = self.gamma_through(freq) @@ -301,18 +336,16 @@ class Calibration: cal.e30 = cal.isolation cal.e10e01 = cal.e00 * cal.e11 - cal.delta_e - cal.e22 = gm7 / ( - gm7 * cal.e11 * gt ** 2 + cal.e10e01 * gt ** 2) - cal.e10e32 = (gm4 - gm6) * ( - 1 - cal.e11 * cal.e22 * gt ** 2) / gt + cal.e22 = gm7 / (gm7 * cal.e11 * gt**2 + cal.e10e01 * gt**2) + cal.e10e32 = (gm4 - gm6) * (1 - cal.e11 * cal.e22 * gt**2) / gt def calc_corrections(self): if not self.isValid1Port(): - logger.warning( - "Tried to calibrate from insufficient data.") + logger.warning("Tried to calibrate from insufficient data.") raise ValueError( "All of short, open and load calibration steps" - "must be completed for calibration to be applied.") + "must be completed for calibration to be applied." + ) logger.debug("Calculating calibration for %d points.", self.size()) for freq, caldata in self.dataset.items(): @@ -324,10 +357,12 @@ class Calibration: self.isCalculated = False logger.error( "Division error - did you use the same measurement" - " for two of short, open and load?") + " for two of short, open and load?" + ) raise ValueError( f"Two of short, open and load returned the same" - f" values at frequency {freq}Hz.") from exc + f" values at frequency {freq}Hz." + ) from exc self.gen_interpolation() self.isCalculated = True @@ -338,25 +373,47 @@ class Calibration: return IDEAL_SHORT logger.debug("Using short calibration set values.") cal_element = self.cal_element - Zsp = complex(0.0, 2.0 * math.pi * freq * ( - cal_element.short_l0 + cal_element.short_l1 * freq + - cal_element.short_l2 * freq**2 + cal_element.short_l3 * freq**3)) + Zsp = complex( + 0.0, + 2.0 + * math.pi + * freq + * ( + cal_element.short_l0 + + cal_element.short_l1 * freq + + cal_element.short_l2 * freq**2 + + cal_element.short_l3 * freq**3 + ), + ) # Referencing https://arxiv.org/pdf/1606.02446.pdf (18) - (21) - return (Zsp / 50.0 - 1.0) / (Zsp / 50.0 + 1.0) * cmath.exp( - complex(0.0, - -4.0 * math.pi * freq * cal_element.short_length)) + return ( + (Zsp / 50.0 - 1.0) + / (Zsp / 50.0 + 1.0) + * cmath.exp( + complex(0.0, -4.0 * math.pi * freq * cal_element.short_length) + ) + ) def gamma_open(self, freq: int) -> complex: if self.cal_element.open_is_ideal: return IDEAL_OPEN logger.debug("Using open calibration set values.") cal_element = self.cal_element - Zop = complex(0.0, 2.0 * math.pi * freq * ( - cal_element.open_c0 + cal_element.open_c1 * freq + - cal_element.open_c2 * freq**2 + cal_element.open_c3 * freq**3)) + Zop = complex( + 0.0, + 2.0 + * math.pi + * freq + * ( + cal_element.open_c0 + + cal_element.open_c1 * freq + + cal_element.open_c2 * freq**2 + + cal_element.open_c3 * freq**3 + ), + ) return ((1.0 - 50.0 * Zop) / (1.0 + 50.0 * Zop)) * cmath.exp( - complex(0.0, - -4.0 * math.pi * freq * cal_element.open_length)) + complex(0.0, -4.0 * math.pi * freq * cal_element.open_length) + ) def gamma_load(self, freq: int) -> complex: if self.cal_element.load_is_ideal: @@ -367,11 +424,17 @@ class Calibration: if cal_element.load_c > 0.0: Zl = cal_element.load_r / complex( 1.0, - 2.0 * cal_element.load_r * math.pi * freq * cal_element.load_c) + 2.0 * cal_element.load_r * math.pi * freq * cal_element.load_c, + ) if cal_element.load_l > 0.0: Zl = Zl + complex(0.0, 2 * math.pi * freq * cal_element.load_l) - return (Zl / 50.0 - 1.0) / (Zl / 50.0 + 1.0) * cmath.exp( - complex(0.0, -4 * math.pi * freq * cal_element.load_length)) + return ( + (Zl / 50.0 - 1.0) + / (Zl / 50.0 + 1.0) + * cmath.exp( + complex(0.0, -4 * math.pi * freq * cal_element.load_length) + ) + ) def gamma_through(self, freq: int) -> complex: if self.cal_element.through_is_ideal: @@ -379,59 +442,103 @@ class Calibration: logger.debug("Using through calibration set values.") cal_element = self.cal_element return cmath.exp( - complex(0.0, -2.0 * math.pi * cal_element.through_length * freq)) + complex(0.0, -2.0 * math.pi * cal_element.through_length * freq) + ) def gen_interpolation(self): - (freq, e00, e11, delta_e, e10e01, e30, e22, e10e32) = zip(*[ - (c.freq, c.e00, c.e11, c.delta_e, c.e10e01, c.e30, c.e22, c.e10e32) - for c in self.dataset.values()]) + (freq, e00, e11, delta_e, e10e01, e30, e22, e10e32) = zip( + *[ + ( + c.freq, + c.e00, + c.e11, + c.delta_e, + c.e10e01, + c.e30, + c.e22, + c.e10e32, + ) + for c in self.dataset.values() + ] + ) self.interp = { - "e00": interp1d(freq, e00, - kind="slinear", bounds_error=False, - fill_value=(e00[0], e00[-1])), - "e11": interp1d(freq, e11, - kind="slinear", bounds_error=False, - fill_value=(e11[0], e11[-1])), - "delta_e": interp1d(freq, delta_e, - kind="slinear", bounds_error=False, - fill_value=(delta_e[0], delta_e[-1])), - "e10e01": interp1d(freq, e10e01, - kind="slinear", bounds_error=False, - fill_value=(e10e01[0], e10e01[-1])), - "e30": interp1d(freq, e30, - kind="slinear", bounds_error=False, - fill_value=(e30[0], e30[-1])), - "e22": interp1d(freq, e22, - kind="slinear", bounds_error=False, - fill_value=(e22[0], e22[-1])), - "e10e32": interp1d(freq, e10e32, - kind="slinear", bounds_error=False, - fill_value=(e10e32[0], e10e32[-1])), + "e00": interp1d( + freq, + e00, + kind="slinear", + bounds_error=False, + fill_value=(e00[0], e00[-1]), + ), + "e11": interp1d( + freq, + e11, + kind="slinear", + bounds_error=False, + fill_value=(e11[0], e11[-1]), + ), + "delta_e": interp1d( + freq, + delta_e, + kind="slinear", + bounds_error=False, + fill_value=(delta_e[0], delta_e[-1]), + ), + "e10e01": interp1d( + freq, + e10e01, + kind="slinear", + bounds_error=False, + fill_value=(e10e01[0], e10e01[-1]), + ), + "e30": interp1d( + freq, + e30, + kind="slinear", + bounds_error=False, + fill_value=(e30[0], e30[-1]), + ), + "e22": interp1d( + freq, + e22, + kind="slinear", + bounds_error=False, + fill_value=(e22[0], e22[-1]), + ), + "e10e32": interp1d( + freq, + e10e32, + kind="slinear", + bounds_error=False, + fill_value=(e10e32[0], e10e32[-1]), + ), } def correct11(self, dp: Datapoint): i = self.interp s11 = (dp.z - i["e00"](dp.freq)) / ( - (dp.z * i["e11"](dp.freq)) - i["delta_e"](dp.freq)) + (dp.z * i["e11"](dp.freq)) - i["delta_e"](dp.freq) + ) return Datapoint(dp.freq, s11.real, s11.imag) def correct21(self, dp: Datapoint, dp11: Datapoint): i = self.interp s21 = (dp.z - i["e30"](dp.freq)) / i["e10e32"](dp.freq) - s21 = s21 * (i["e10e01"](dp.freq) / (i["e11"](dp.freq) - * dp11.z - i["delta_e"](dp.freq))) + s21 = s21 * ( + i["e10e01"](dp.freq) + / (i["e11"](dp.freq) * dp11.z - i["delta_e"](dp.freq)) + ) return Datapoint(dp.freq, s21.real, s21.imag) def save(self, filename: str): self.dataset.notes = "\n".join(self.notes) if not self.isValid1Port(): raise ValueError("Not a valid calibration") - with open(filename, mode="w", encoding='utf-8') as calfile: + with open(filename, mode="w", encoding="utf-8") as calfile: calfile.write(str(self.dataset)) def load(self, filename): self.source = os.path.basename(filename) - with open(filename, encoding='utf-8') as calfile: + with open(filename, encoding="utf-8") as calfile: self.dataset = CalDataSet().from_str(calfile.read()) self.notes = self.dataset.notes.splitlines() diff --git a/src/NanoVNASaver/Charts/CLogMag.py b/src/NanoVNASaver/Charts/CLogMag.py index 2975b16..dd45203 100644 --- a/src/NanoVNASaver/Charts/CLogMag.py +++ b/src/NanoVNASaver/Charts/CLogMag.py @@ -61,20 +61,24 @@ class CombinedLogMagChart(LogMagChart): def drawChart(self, qp: QtGui.QPainter): qp.setPen(QtGui.QPen(Chart.color.text)) - qp.drawText(int(self.dim.width // 2) - 20, - 15, - f"{self.name} {self.name_unit}") + qp.drawText( + int(self.dim.width // 2) - 20, 15, f"{self.name} {self.name_unit}" + ) qp.drawText(10, 15, "S11") qp.drawText(self.leftMargin + self.dim.width - 8, 15, "S21") qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin, - self.topMargin - 5, - self.leftMargin, - self.topMargin + self.dim.height + 5) - qp.drawLine(self.leftMargin - 5, - self.topMargin + self.dim.height, - self.leftMargin + self.dim.width, - self.topMargin + self.dim.height) + qp.drawLine( + self.leftMargin, + self.topMargin - 5, + self.leftMargin, + self.topMargin + self.dim.height + 5, + ) + qp.drawLine( + self.leftMargin - 5, + self.topMargin + self.dim.height, + self.leftMargin + self.dim.width, + self.topMargin + self.dim.height, + ) def drawValues(self, qp: QtGui.QPainter): if len(self.data11) == 0 and len(self.reference11) == 0: @@ -117,8 +121,12 @@ class CombinedLogMagChart(LogMagChart): pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) - qp.drawLine(self.leftMargin + self.dim.width - 20, 9, - self.leftMargin + self.dim.width - 15, 9) + qp.drawLine( + self.leftMargin + self.dim.width - 20, + 9, + self.leftMargin + self.dim.width - 15, + 9, + ) if self.reference11: c = QtGui.QColor(Chart.color.reference) @@ -132,8 +140,12 @@ class CombinedLogMagChart(LogMagChart): pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) - qp.drawLine(self.leftMargin + self.dim.width - 20, 14, - self.leftMargin + self.dim.width - 15, 14) + qp.drawLine( + self.leftMargin + self.dim.width - 20, + 14, + self.leftMargin + self.dim.width - 15, + 14, + ) self.drawData(qp, self.data11, Chart.color.sweep) self.drawData(qp, self.data21, Chart.color.sweep_secondary) diff --git a/src/NanoVNASaver/Charts/Chart.py b/src/NanoVNASaver/Charts/Chart.py index 1687120..739858c 100644 --- a/src/NanoVNASaver/Charts/Chart.py +++ b/src/NanoVNASaver/Charts/Chart.py @@ -36,13 +36,16 @@ logger = logging.getLogger(__name__) class ChartColors: # pylint: disable=too-many-instance-attributes background: QColor = field(default_factory=lambda: QColor(QtCore.Qt.white)) foreground: QColor = field( - default_factory=lambda: QColor(QtCore.Qt.lightGray)) + default_factory=lambda: QColor(QtCore.Qt.lightGray) + ) reference: QColor = field(default_factory=lambda: QColor(0, 0, 255, 64)) reference_secondary: QColor = field( - default_factory=lambda: QColor(0, 0, 192, 48)) + default_factory=lambda: QColor(0, 0, 192, 48) + ) sweep: QColor = field(default_factory=lambda: QColor(QtCore.Qt.darkYellow)) sweep_secondary: QColor = field( - default_factory=lambda: QColor(QtCore.Qt.darkMagenta)) + default_factory=lambda: QColor(QtCore.Qt.darkMagenta) + ) swr: QColor = field(default_factory=lambda: QColor(255, 0, 0, 128)) text: QColor = field(default_factory=lambda: QColor(QtCore.Qt.black)) bands: QColor = field(default_factory=lambda: QColor(128, 128, 128, 48)) @@ -97,8 +100,7 @@ class ChartMarker(QtWidgets.QWidget): if text and Defaults.cfg.chart.marker_label: text_width = self.qp.fontMetrics().horizontalAdvance(text) - self.qp.drawText(x - int(text_width // 2), - y - 3 - offset, text) + self.qp.drawText(x - int(text_width // 2), y - 3 - offset, text) class Chart(QtWidgets.QWidget): @@ -109,7 +111,7 @@ class Chart(QtWidgets.QWidget): def __init__(self, name): super().__init__() self.name = name - self.sweepTitle = '' + self.sweepTitle = "" self.leftMargin = 30 self.rightMargin = 20 @@ -130,7 +132,8 @@ class Chart(QtWidgets.QWidget): self.action_popout = QtWidgets.QAction("Popout chart") self.action_popout.triggered.connect( - lambda: self.popoutRequested.emit(self)) + lambda: self.popoutRequested.emit(self) + ) self.addAction(self.action_popout) self.action_save_screenshot = QtWidgets.QAction("Save image") @@ -230,7 +233,9 @@ class Chart(QtWidgets.QWidget): self.zoomTo( self.dragbox.pos_start[0], self.dragbox.pos_start[1], - a0.x(), a0.y()) + a0.x(), + a0.y(), + ) self.dragbox.state = False self.dragbox.pos = (-1, -1) self.dragbox.pos_start = (0, 0) @@ -262,7 +267,7 @@ class Chart(QtWidgets.QWidget): int(self.leftMargin + ratio_x * factor_x), int(self.topMargin + ratio_y * factor_y), int(self.leftMargin + self.dim.width - (1 - ratio_x) * factor_x), - int(self.topMargin + self.dim.height - (1 - ratio_y) * factor_y) + int(self.topMargin + self.dim.height - (1 - ratio_y) * factor_y), ) a0.accept() @@ -272,8 +277,10 @@ class Chart(QtWidgets.QWidget): def saveScreenshot(self): logger.info("Saving %s to file...", self.name) filename, _ = QtWidgets.QFileDialog.getSaveFileName( - parent=self, caption="Save image", - filter="PNG (*.png);;All files (*.*)") + parent=self, + caption="Save image", + filter="PNG (*.png);;All files (*.*)", + ) logger.debug("Filename: %s", filename) if not filename: @@ -314,9 +321,9 @@ class Chart(QtWidgets.QWidget): self.update() @staticmethod - def drawMarker(x: int, y: int, - qp: QtGui.QPainter, color: QtGui.QColor, - number: int = 0): + def drawMarker( + x: int, y: int, qp: QtGui.QPainter, color: QtGui.QColor, number: int = 0 + ): cmarker = ChartMarker(qp) cmarker.draw(x, y, color, f"{number}") diff --git a/src/NanoVNASaver/Charts/Frequency.py b/src/NanoVNASaver/Charts/Frequency.py index aa0fc5c..dff4ad4 100644 --- a/src/NanoVNASaver/Charts/Frequency.py +++ b/src/NanoVNASaver/Charts/Frequency.py @@ -25,9 +25,12 @@ from PyQt5 import QtWidgets, QtGui, QtCore from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Formatting import ( - parse_frequency, parse_value, - format_frequency_chart, format_frequency_chart_2, - format_y_axis) + parse_frequency, + parse_value, + format_frequency_chart, + format_frequency_chart_2, + format_y_axis, +) from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.SITools import Format, Value @@ -35,7 +38,6 @@ logger = logging.getLogger(__name__) class FrequencyChart(Chart): - def __init__(self, name): super().__init__(name) self.maxFrequency = 100000000 @@ -79,11 +81,13 @@ class FrequencyChart(Chart): self.action_automatic.setCheckable(True) self.action_automatic.setChecked(True) self.action_automatic.changed.connect( - lambda: self.setFixedSpan(self.action_fixed_span.isChecked())) + lambda: self.setFixedSpan(self.action_fixed_span.isChecked()) + ) self.action_fixed_span = QtWidgets.QAction("Fixed span") self.action_fixed_span.setCheckable(True) self.action_fixed_span.changed.connect( - lambda: self.setFixedSpan(self.action_fixed_span.isChecked())) + lambda: self.setFixedSpan(self.action_fixed_span.isChecked()) + ) mode_group.addAction(self.action_automatic) mode_group.addAction(self.action_fixed_span) self.x_menu.addAction(self.action_automatic) @@ -91,11 +95,13 @@ class FrequencyChart(Chart): self.x_menu.addSeparator() self.action_set_fixed_start = QtWidgets.QAction( - f"Start ({format_frequency_chart(self.minFrequency)})") + f"Start ({format_frequency_chart(self.minFrequency)})" + ) self.action_set_fixed_start.triggered.connect(self.setMinimumFrequency) self.action_set_fixed_stop = QtWidgets.QAction( - f"Stop ({format_frequency_chart(self.maxFrequency)})") + f"Stop ({format_frequency_chart(self.maxFrequency)})" + ) self.action_set_fixed_stop.triggered.connect(self.setMaximumFrequency) self.x_menu.addAction(self.action_set_fixed_start) @@ -110,9 +116,11 @@ class FrequencyChart(Chart): frequency_mode_group.addAction(self.action_set_linear_x) frequency_mode_group.addAction(self.action_set_logarithmic_x) self.action_set_linear_x.triggered.connect( - lambda: self.setLogarithmicX(False)) + lambda: self.setLogarithmicX(False) + ) self.action_set_logarithmic_x.triggered.connect( - lambda: self.setLogarithmicX(True)) + lambda: self.setLogarithmicX(True) + ) self.action_set_linear_x.setChecked(True) self.x_menu.addAction(self.action_set_linear_x) self.x_menu.addAction(self.action_set_logarithmic_x) @@ -122,11 +130,13 @@ class FrequencyChart(Chart): self.y_action_automatic.setCheckable(True) self.y_action_automatic.setChecked(True) self.y_action_automatic.changed.connect( - lambda: self.setFixedValues(self.y_action_fixed_span.isChecked())) + lambda: self.setFixedValues(self.y_action_fixed_span.isChecked()) + ) self.y_action_fixed_span = QtWidgets.QAction("Fixed span") self.y_action_fixed_span.setCheckable(True) self.y_action_fixed_span.changed.connect( - lambda: self.setFixedValues(self.y_action_fixed_span.isChecked())) + lambda: self.setFixedValues(self.y_action_fixed_span.isChecked()) + ) mode_group = QtWidgets.QActionGroup(self) mode_group.addAction(self.y_action_automatic) mode_group.addAction(self.y_action_fixed_span) @@ -135,11 +145,13 @@ class FrequencyChart(Chart): self.y_menu.addSeparator() self.action_set_fixed_minimum = QtWidgets.QAction( - f"Minimum ({self.minDisplayValue})") + f"Minimum ({self.minDisplayValue})" + ) self.action_set_fixed_minimum.triggered.connect(self.setMinimumValue) self.action_set_fixed_maximum = QtWidgets.QAction( - f"Maximum ({self.maxDisplayValue})") + f"Maximum ({self.maxDisplayValue})" + ) self.action_set_fixed_maximum.triggered.connect(self.setMaximumValue) self.y_menu.addAction(self.action_set_fixed_maximum) @@ -155,9 +167,11 @@ class FrequencyChart(Chart): vertical_mode_group.addAction(self.action_set_linear_y) vertical_mode_group.addAction(self.action_set_logarithmic_y) self.action_set_linear_y.triggered.connect( - lambda: self.setLogarithmicY(False)) + lambda: self.setLogarithmicY(False) + ) self.action_set_logarithmic_y.triggered.connect( - lambda: self.setLogarithmicY(True)) + lambda: self.setLogarithmicY(True) + ) self.action_set_linear_y.setChecked(True) self.y_menu.addAction(self.action_set_linear_y) self.y_menu.addAction(self.action_set_logarithmic_y) @@ -168,16 +182,21 @@ class FrequencyChart(Chart): self.menu.addAction(self.action_save_screenshot) self.action_popout = QtWidgets.QAction("Popout chart") self.action_popout.triggered.connect( - lambda: self.popoutRequested.emit(self)) + lambda: self.popoutRequested.emit(self) + ) self.menu.addAction(self.action_popout) self.setFocusPolicy(QtCore.Qt.ClickFocus) self.setMinimumSize( self.dim.width + self.rightMargin + self.leftMargin, - self.dim.height + self.topMargin + self.bottomMargin) + self.dim.height + self.topMargin + self.bottomMargin, + ) self.setSizePolicy( - QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) + QtWidgets.QSizePolicy( + QtWidgets.QSizePolicy.MinimumExpanding, + QtWidgets.QSizePolicy.MinimumExpanding, + ) + ) pal = QtGui.QPalette() pal.setColor(QtGui.QPalette.Background, Chart.color.background) self.setPalette(pal) @@ -197,13 +216,17 @@ class FrequencyChart(Chart): def contextMenuEvent(self, event): self.action_set_fixed_start.setText( - f"Start ({format_frequency_chart(self.minFrequency)})") + f"Start ({format_frequency_chart(self.minFrequency)})" + ) self.action_set_fixed_stop.setText( - f"Stop ({format_frequency_chart(self.maxFrequency)})") + f"Stop ({format_frequency_chart(self.maxFrequency)})" + ) self.action_set_fixed_minimum.setText( - f"Minimum ({self.minDisplayValue})") + f"Minimum ({self.minDisplayValue})" + ) self.action_set_fixed_maximum.setText( - f"Maximum ({self.maxDisplayValue})") + f"Maximum ({self.maxDisplayValue})" + ) if self.fixedSpan: self.action_fixed_span.setChecked(True) @@ -242,8 +265,11 @@ class FrequencyChart(Chart): def setMinimumFrequency(self): min_freq_str, selected = QtWidgets.QInputDialog.getText( - self, "Start frequency", - "Set start frequency", text=str(self.minFrequency)) + self, + "Start frequency", + "Set start frequency", + text=str(self.minFrequency), + ) if not selected: return span = abs(self.maxFrequency - self.minFrequency) @@ -258,8 +284,11 @@ class FrequencyChart(Chart): def setMaximumFrequency(self): max_freq_str, selected = QtWidgets.QInputDialog.getText( - self, "Stop frequency", - "Set stop frequency", text=str(self.maxFrequency)) + self, + "Stop frequency", + "Set stop frequency", + text=str(self.maxFrequency), + ) if not selected: return span = abs(self.maxFrequency - self.minFrequency) @@ -274,9 +303,11 @@ class FrequencyChart(Chart): def setMinimumValue(self): text, selected = QtWidgets.QInputDialog.getText( - self, "Minimum value", + self, + "Minimum value", "Set minimum value", - text=format_y_axis(self.minDisplayValue, self.name_unit)) + text=format_y_axis(self.minDisplayValue, self.name_unit), + ) if not selected: return min_val = parse_value(text) @@ -292,9 +323,11 @@ class FrequencyChart(Chart): def setMaximumValue(self): text, selected = QtWidgets.QInputDialog.getText( - self, "Maximum value", + self, + "Maximum value", "Set maximum value", - text=format_y_axis(self.maxDisplayValue, self.name_unit)) + text=format_y_axis(self.maxDisplayValue, self.name_unit), + ) if not selected: return max_val = parse_value(text) @@ -323,18 +356,21 @@ class FrequencyChart(Chart): if self.logarithmicX: span = math.log(self.fstop) - math.log(self.fstart) return self.leftMargin + round( - self.dim.width * (math.log(d.freq) - - math.log(self.fstart)) / span) + self.dim.width + * (math.log(d.freq) - math.log(self.fstart)) + / span + ) return self.leftMargin + round( - self.dim.width * (d.freq - self.fstart) / span) + self.dim.width * (d.freq - self.fstart) / span + ) return math.floor(self.width() / 2) def getYPosition(self, d: Datapoint) -> int: try: - return ( - self.topMargin + round( - (self.maxValue - self.value_function(d)) / - self.span * self.dim.height) + return self.topMargin + round( + (self.maxValue - self.value_function(d)) + / self.span + * self.dim.height ) except ValueError: return self.topMargin @@ -410,9 +446,12 @@ class FrequencyChart(Chart): if self.dragbox.move_x != -1 and self.dragbox.move_y != -1: dx = self.dragbox.move_x - a0.x() dy = self.dragbox.move_y - a0.y() - self.zoomTo(self.leftMargin + dx, self.topMargin + dy, - self.leftMargin + self.dim.width + dx, - self.topMargin + self.dim.height + dy) + self.zoomTo( + self.leftMargin + dx, + self.topMargin + dy, + self.leftMargin + self.dim.width + dx, + self.topMargin + self.dim.height + dy, + ) self.dragbox.move_x = a0.x() self.dragbox.move_y = a0.y() @@ -436,10 +475,10 @@ class FrequencyChart(Chart): m.setFrequency(str(f)) def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: - self.dim.width = ( - a0.size().width() - self.rightMargin - self.leftMargin) + self.dim.width = a0.size().width() - self.rightMargin - self.leftMargin self.dim.height = ( - a0.size().height() - self.bottomMargin - self.topMargin) + a0.size().height() - self.bottomMargin - self.topMargin + ) self.update() def paintEvent(self, _: QtGui.QPaintEvent) -> None: @@ -452,24 +491,30 @@ class FrequencyChart(Chart): qp.end() def _data_oob(self, data: List[Datapoint]) -> bool: - return (data[0].freq > self.fstop or self.data[-1].freq < self.fstart) + return data[0].freq > self.fstop or self.data[-1].freq < self.fstart def _check_frequency_boundaries(self, qp: QtGui.QPainter): - if (self.data and self._data_oob(self.data) and - (not self.reference or self._data_oob(self.reference))): + if ( + self.data + and self._data_oob(self.data) + and (not self.reference or self._data_oob(self.reference)) + ): # Data outside frequency range qp.setBackgroundMode(QtCore.Qt.OpaqueMode) qp.setBackground(Chart.color.background) qp.setPen(Chart.color.text) - qp.drawText(self.leftMargin + int(self.dim.width // 2) - 70, - self.topMargin + int(self.dim.height // 2) - 20, - "Data outside frequency span") + qp.drawText( + self.leftMargin + int(self.dim.width // 2) - 70, + self.topMargin + int(self.dim.height // 2) - 20, + "Data outside frequency span", + ) def drawDragbog(self, qp: QtGui.QPainter): dashed_pen = QtGui.QPen(Chart.color.foreground, 1, QtCore.Qt.DashLine) qp.setPen(dashed_pen) top_left = QtCore.QPoint( - self.dragbox.pos_start[0], self.dragbox.pos_start[1]) + self.dragbox.pos_start[0], self.dragbox.pos_start[1] + ) bottom_right = QtCore.QPoint(self.dragbox.pos[0], self.dragbox.pos[1]) rect = QtCore.QRect(top_left, bottom_right) qp.drawRect(rect) @@ -481,14 +526,18 @@ class FrequencyChart(Chart): headline += f" ({self.name_unit})" qp.drawText(3, 15, headline) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin, - 20, - self.leftMargin, - self.topMargin + self.dim.height + 5) - qp.drawLine(self.leftMargin - 5, - self.topMargin + self.dim.height, - self.leftMargin + self.dim.width, - self.topMargin + self.dim.height) + qp.drawLine( + self.leftMargin, + 20, + self.leftMargin, + self.topMargin + self.dim.height + 5, + ) + qp.drawLine( + self.leftMargin - 5, + self.topMargin + self.dim.height, + self.leftMargin + self.dim.width, + self.topMargin + self.dim.height, + ) self.drawTitle(qp) def drawValues(self, qp: QtGui.QPainter): @@ -514,7 +563,8 @@ class FrequencyChart(Chart): if span == 0: logger.info( "Span is zero for %s-Chart, setting to a small value.", - self.name) + self.name, + ) span = 1e-15 self.span = span @@ -522,23 +572,30 @@ class FrequencyChart(Chart): fmt = Format(max_nr_digits=1) for i in range(target_ticks): val = min_value + (i / target_ticks) * span - y = self.topMargin + \ - round((self.maxValue - val) / self.span * self.dim.height) + y = self.topMargin + round( + (self.maxValue - val) / self.span * self.dim.height + ) qp.setPen(Chart.color.text) if val != min_value: valstr = str(Value(val, fmt=fmt)) qp.drawText(3, y + 3, valstr) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width, y) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width, y + ) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.dim.width, self.topMargin) + qp.drawLine( + self.leftMargin - 5, + self.topMargin, + self.leftMargin + self.dim.width, + self.topMargin, + ) qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 4, str(Value(max_value, fmt=fmt))) - qp.drawText(3, self.dim.height + self.topMargin, - str(Value(min_value, fmt=fmt))) + qp.drawText( + 3, self.dim.height + self.topMargin, str(Value(min_value, fmt=fmt)) + ) self.drawFrequencyTicks(qp) self.drawData(qp, self.data, Chart.color.sweep) @@ -574,27 +631,31 @@ class FrequencyChart(Chart): else: my_format_frequency = format_frequency_chart_2 - qp.drawText(self.leftMargin - 20, - self.topMargin + self.dim.height + 15, - my_format_frequency(self.fstart)) + qp.drawText( + self.leftMargin - 20, + self.topMargin + self.dim.height + 15, + my_format_frequency(self.fstart), + ) for i in range(ticks): x = self.leftMargin + round((i + 1) * self.dim.width / ticks) if self.logarithmicX: fspan = math.log(self.fstop) - math.log(self.fstart) freq = round( - math.exp( - ((i + 1) * fspan / ticks) + - math.log(self.fstart))) + math.exp(((i + 1) * fspan / ticks) + math.log(self.fstart)) + ) else: freq = round(fspan / ticks * (i + 1) + self.fstart) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(x, self.topMargin, x, - self.topMargin + self.dim.height + 5) + qp.drawLine( + x, self.topMargin, x, self.topMargin + self.dim.height + 5 + ) qp.setPen(Chart.color.text) - qp.drawText(x - 20, - self.topMargin + self.dim.height + 15, - my_format_frequency(freq)) + qp.drawText( + x - 20, + self.topMargin + self.dim.height + 15, + my_format_frequency(freq), + ) def drawBands(self, qp, fstart, fstop): qp.setBrush(self.bands.color) @@ -608,17 +669,24 @@ class FrequencyChart(Chart): # don't draw if either band not in chart or completely in band if start < fstart < fstop < end or end < fstart or start > fstop: continue - x_start = max(self.leftMargin + 1, - self.getXPosition(Datapoint(start, 0, 0))) - x_stop = min(self.leftMargin + self.dim.width, - self.getXPosition(Datapoint(end, 0, 0))) - qp.drawRect(x_start, - self.topMargin, - x_stop - x_start, - self.dim.height) + x_start = max( + self.leftMargin + 1, self.getXPosition(Datapoint(start, 0, 0)) + ) + x_stop = min( + self.leftMargin + self.dim.width, + self.getXPosition(Datapoint(end, 0, 0)), + ) + qp.drawRect( + x_start, self.topMargin, x_stop - x_start, self.dim.height + ) - def drawData(self, qp: QtGui.QPainter, data: List[Datapoint], - color: QtGui.QColor, y_function=None): + def drawData( + self, + qp: QtGui.QPainter, + data: List[Datapoint], + color: QtGui.QColor, + y_function=None, + ): if y_function is None: y_function = self.getYPosition pen = QtGui.QPen(color) @@ -643,8 +711,7 @@ class FrequencyChart(Chart): if self.isPlotable(prevx, prevy): qp.drawLine(x, y, prevx, prevy) else: - new_x, new_y = self.getPlotable( - x, y, prevx, prevy) + new_x, new_y = self.getPlotable(x, y, prevx, prevy) qp.drawLine(x, y, new_x, new_y) elif self.isPlotable(prevx, prevy): new_x, new_y = self.getPlotable(prevx, prevy, x, y) @@ -663,13 +730,17 @@ class FrequencyChart(Chart): x = self.getXPosition(data[m.location]) y = y_function(data[m.location]) if self.isPlotable(x, y): - self.drawMarker(x, y, qp, m.color, - self.markers.index(m) + 1) + self.drawMarker( + x, y, qp, m.color, self.markers.index(m) + 1 + ) def isPlotable(self, x, y): - return y is not None and x is not None and \ - self.leftMargin <= x <= self.leftMargin + self.dim.width and \ - self.topMargin <= y <= self.topMargin + self.dim.height + return ( + y is not None + and x is not None + and self.leftMargin <= x <= self.leftMargin + self.dim.width + and self.topMargin <= y <= self.topMargin + self.dim.height + ) def getPlotable(self, x, y, distantx, distanty): p1 = np.array([x, y]) @@ -680,8 +751,12 @@ class FrequencyChart(Chart): p4 = np.array([self.leftMargin + self.dim.width, self.topMargin]) elif distanty > self.topMargin + self.dim.height: p3 = np.array([self.leftMargin, self.topMargin + self.dim.height]) - p4 = np.array([self.leftMargin + self.dim.width, - self.topMargin + self.dim.height]) + p4 = np.array( + [ + self.leftMargin + self.dim.width, + self.topMargin + self.dim.height, + ] + ) else: return x, y @@ -730,10 +805,14 @@ class FrequencyChart(Chart): m = self.getActiveMarker() if m is not None and a0.modifiers() == QtCore.Qt.NoModifier: if a0.key() in [QtCore.Qt.Key_Down, QtCore.Qt.Key_Left]: - m.frequencyInput.keyPressEvent(QtGui.QKeyEvent( - a0.type(), QtCore.Qt.Key_Down, a0.modifiers())) + m.frequencyInput.keyPressEvent( + QtGui.QKeyEvent( + a0.type(), QtCore.Qt.Key_Down, a0.modifiers() + ) + ) elif a0.key() in [QtCore.Qt.Key_Up, QtCore.Qt.Key_Right]: - m.frequencyInput.keyPressEvent(QtGui.QKeyEvent( - a0.type(), QtCore.Qt.Key_Up, a0.modifiers())) + m.frequencyInput.keyPressEvent( + QtGui.QKeyEvent(a0.type(), QtCore.Qt.Key_Up, a0.modifiers()) + ) else: super().keyPressEvent(a0) diff --git a/src/NanoVNASaver/Charts/GroupDelay.py b/src/NanoVNASaver/Charts/GroupDelay.py index 3ebac16..1ef3ad0 100644 --- a/src/NanoVNASaver/Charts/GroupDelay.py +++ b/src/NanoVNASaver/Charts/GroupDelay.py @@ -27,6 +27,7 @@ from PyQt5 import QtGui from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.RFTools import Datapoint from .Frequency import FrequencyChart + logger = logging.getLogger(__name__) @@ -124,23 +125,30 @@ class GroupDelayChart(FrequencyChart): tickcount = math.floor(self.dim.height / 60) for i in range(tickcount): delay = min_delay + span * i / tickcount - y = self.topMargin + \ - round((self.maxDelay - delay) / self.span * self.dim.height) + y = self.topMargin + round( + (self.maxDelay - delay) / self.span * self.dim.height + ) if delay not in {min_delay, max_delay}: qp.setPen(QtGui.QPen(Chart.color.text)) # TODO use format class - digits = 0 if delay == 0 else max( - 0, min(2, math.floor(3 - math.log10(abs(delay))))) + digits = ( + 0 + if delay == 0 + else max(0, min(2, math.floor(3 - math.log10(abs(delay))))) + ) delaystr = str(round(delay, digits if digits != 0 else None)) qp.drawText(3, y + 3, delaystr) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width, y) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width, y + ) - qp.drawLine(self.leftMargin - 5, - self.topMargin, - self.leftMargin + self.dim.width, - self.topMargin) + qp.drawLine( + self.leftMargin - 5, + self.topMargin, + self.leftMargin + self.dim.width, + self.topMargin, + ) qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 5, str(max_delay)) qp.drawText(3, self.dim.height + self.topMargin, str(min_delay)) @@ -153,15 +161,20 @@ class GroupDelayChart(FrequencyChart): self.drawFrequencyTicks(qp) - self.draw_data(qp, Chart.color.sweep, - self.data, self.groupDelay) - self.draw_data(qp, Chart.color.reference, - self.reference, self.groupDelayReference) + self.draw_data(qp, Chart.color.sweep, self.data, self.groupDelay) + self.draw_data( + qp, Chart.color.reference, self.reference, self.groupDelayReference + ) self.drawMarkers(qp) - def draw_data(self, qp: QtGui.QPainter, color: QtGui.QColor, - data: List[Datapoint], delay: List[Datapoint]): + def draw_data( + self, + qp: QtGui.QPainter, + color: QtGui.QColor, + data: List[Datapoint], + delay: List[Datapoint], + ): pen = QtGui.QPen(color) pen.setWidth(self.dim.point) line_pen = QtGui.QPen(color) @@ -200,7 +213,8 @@ class GroupDelayChart(FrequencyChart): def getYPositionFromDelay(self, delay: float) -> int: return self.topMargin + int( - (self.maxDelay - delay) / self.span * self.dim.height) + (self.maxDelay - delay) / self.span * self.dim.height + ) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin diff --git a/src/NanoVNASaver/Charts/LogMag.py b/src/NanoVNASaver/Charts/LogMag.py index da8099f..5237d1c 100644 --- a/src/NanoVNASaver/Charts/LogMag.py +++ b/src/NanoVNASaver/Charts/LogMag.py @@ -115,8 +115,12 @@ class LogMagChart(FrequencyChart): self.draw_db_lines(qp, self.maxValue, self.minValue, ticks) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.dim.width, self.topMargin) + qp.drawLine( + self.leftMargin - 5, + self.topMargin, + self.leftMargin + self.dim.width, + self.topMargin, + ) qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 4, f"{self.maxValue}") qp.drawText(3, self.dim.height + self.topMargin, f"{self.minValue}") @@ -127,14 +131,17 @@ class LogMagChart(FrequencyChart): for i in range(ticks.count): db = ticks.first + i * ticks.step y = self.topMargin + round( - (maxValue - db) / self.span * self.dim.height) + (maxValue - db) / self.span * self.dim.height + ) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width, y) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width, y + ) if db > minValue and db != maxValue: qp.setPen(QtGui.QPen(Chart.color.text)) - qp.drawText(3, y + 4, - f"{round(db, 1)}" if ticks.step < 1 else f"{db}") + qp.drawText( + 3, y + 4, f"{round(db, 1)}" if ticks.step < 1 else f"{db}" + ) def draw_swr_markers(self, qp) -> None: qp.setPen(Chart.color.swr) @@ -145,9 +152,9 @@ class LogMagChart(FrequencyChart): if self.isInverted: logMag = logMag * -1 y = self.topMargin + round( - (self.maxValue - logMag) / self.span * self.dim.height) - qp.drawLine(self.leftMargin, y, - self.leftMargin + self.dim.width, y) + (self.maxValue - logMag) / self.span * self.dim.height + ) + qp.drawLine(self.leftMargin, y, self.leftMargin + self.dim.width, y) qp.drawText(self.leftMargin + 3, y - 1, f"VSWR: {vswr}") def getYPosition(self, d: Datapoint) -> int: @@ -155,7 +162,8 @@ class LogMagChart(FrequencyChart): if math.isinf(logMag): return self.topMargin return self.topMargin + int( - (self.maxValue - logMag) / self.span * self.dim.height) + (self.maxValue - logMag) / self.span * self.dim.height + ) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin diff --git a/src/NanoVNASaver/Charts/Magnitude.py b/src/NanoVNASaver/Charts/Magnitude.py index 0c8c9df..15404c7 100644 --- a/src/NanoVNASaver/Charts/Magnitude.py +++ b/src/NanoVNASaver/Charts/Magnitude.py @@ -25,6 +25,7 @@ from PyQt5 import QtGui from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Charts.Frequency import FrequencyChart + logger = logging.getLogger(__name__) @@ -78,21 +79,28 @@ class MagnitudeChart(FrequencyChart): target_ticks = int(self.dim.height // 60) for i in range(target_ticks): val = min_value + i / target_ticks * self.span - y = self.topMargin + int((self.maxValue - val) / self.span - * self.dim.height) + y = self.topMargin + int( + (self.maxValue - val) / self.span * self.dim.height + ) qp.setPen(Chart.color.text) if val != min_value: digits = max(0, min(2, math.floor(3 - math.log10(abs(val))))) - vswrstr = (str(round(val)) if digits == 0 else - str(round(val, digits))) + vswrstr = ( + str(round(val)) if digits == 0 else str(round(val, digits)) + ) qp.drawText(3, y + 3, vswrstr) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width, y) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width, y + ) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.dim.width, self.topMargin) + qp.drawLine( + self.leftMargin - 5, + self.topMargin, + self.leftMargin + self.dim.width, + self.topMargin, + ) qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 4, str(max_value)) qp.drawText(3, self.dim.height + self.topMargin, str(min_value)) @@ -103,10 +111,10 @@ class MagnitudeChart(FrequencyChart): if vswr <= 1: continue mag = (vswr - 1) / (vswr + 1) - y = self.topMargin + int((self.maxValue - mag) / self.span - * self.dim.height) - qp.drawLine(self.leftMargin, y, - self.leftMargin + self.dim.width, y) + y = self.topMargin + int( + (self.maxValue - mag) / self.span * self.dim.height + ) + qp.drawLine(self.leftMargin, y, self.leftMargin + self.dim.width, y) qp.drawText(self.leftMargin + 3, y - 1, f"VSWR: {vswr}") self.drawData(qp, self.data, Chart.color.sweep) @@ -116,7 +124,8 @@ class MagnitudeChart(FrequencyChart): def getYPosition(self, d: Datapoint) -> int: mag = self.magnitude(d) return self.topMargin + int( - (self.maxValue - mag) / self.span * self.dim.height) + (self.maxValue - mag) / self.span * self.dim.height + ) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin diff --git a/src/NanoVNASaver/Charts/MagnitudeZ.py b/src/NanoVNASaver/Charts/MagnitudeZ.py index a6fd2ac..4e4fb85 100644 --- a/src/NanoVNASaver/Charts/MagnitudeZ.py +++ b/src/NanoVNASaver/Charts/MagnitudeZ.py @@ -23,8 +23,7 @@ from typing import List from PyQt5 import QtGui from NanoVNASaver.RFTools import Datapoint -from NanoVNASaver.SITools import ( - Format, Value, round_ceil, round_floor) +from NanoVNASaver.SITools import Format, Value, round_ceil, round_floor from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Charts.Frequency import FrequencyChart from NanoVNASaver.Charts.LogMag import LogMagChart @@ -57,8 +56,10 @@ class MagnitudeZChart(FrequencyChart): if self.fixedValues: self.maxValue = self.maxDisplayValue self.minValue = ( - max(self.minDisplayValue, 0.01) if self.logarithmicY else - self.minDisplayValue) + max(self.minDisplayValue, 0.01) + if self.logarithmicY + else self.minDisplayValue + ) else: # Find scaling self.minValue = 100 @@ -92,15 +93,18 @@ class MagnitudeZChart(FrequencyChart): for i in range(horizontal_ticks): y = self.topMargin + round(i * self.dim.height / horizontal_ticks) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width + 5, y) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width + 5, y + ) qp.setPen(QtGui.QPen(Chart.color.text)) val = Value(self.valueAtPosition(y)[0], fmt=fmt) qp.drawText(3, y + 4, str(val)) - qp.drawText(3, - self.dim.height + self.topMargin, - str(Value(self.minValue, fmt=fmt))) + qp.drawText( + 3, + self.dim.height + self.topMargin, + str(Value(self.minValue, fmt=fmt)), + ) self.drawFrequencyTicks(qp) @@ -116,18 +120,22 @@ class MagnitudeZChart(FrequencyChart): if self.logarithmicY: span = math.log(self.maxValue) - math.log(self.minValue) return self.topMargin + int( - (math.log(self.maxValue) - math.log(mag)) / - span * self.dim.height) + (math.log(self.maxValue) - math.log(mag)) + / span + * self.dim.height + ) return self.topMargin + int( - (self.maxValue - mag) / self.span * self.dim.height) + (self.maxValue - mag) / self.span * self.dim.height + ) return self.topMargin def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin if self.logarithmicY: span = math.log(self.maxValue) - math.log(self.minValue) - val = math.exp(math.log(self.maxValue) - - absy * span / self.dim.height) + val = math.exp( + math.log(self.maxValue) - absy * span / self.dim.height + ) else: val = self.maxValue - (absy / self.dim.height * self.span) return [val] diff --git a/src/NanoVNASaver/Charts/MagnitudeZSeries.py b/src/NanoVNASaver/Charts/MagnitudeZSeries.py index 816ca83..983bbd5 100644 --- a/src/NanoVNASaver/Charts/MagnitudeZSeries.py +++ b/src/NanoVNASaver/Charts/MagnitudeZSeries.py @@ -1,4 +1,3 @@ - # NanoVNASaver # # A python program to view and export Touchstone data from a NanoVNA @@ -27,7 +26,6 @@ logger = logging.getLogger(__name__) class MagnitudeZSeriesChart(MagnitudeZChart): - @staticmethod def magnitude(p: Datapoint) -> float: return abs(p.seriesImpedance()) diff --git a/src/NanoVNASaver/Charts/MagnitudeZShunt.py b/src/NanoVNASaver/Charts/MagnitudeZShunt.py index 3736e3d..8c59760 100644 --- a/src/NanoVNASaver/Charts/MagnitudeZShunt.py +++ b/src/NanoVNASaver/Charts/MagnitudeZShunt.py @@ -1,4 +1,3 @@ - # NanoVNASaver # # A python program to view and export Touchstone data from a NanoVNA @@ -26,7 +25,6 @@ logger = logging.getLogger(__name__) class MagnitudeZShuntChart(MagnitudeZChart): - @staticmethod def magnitude(p: Datapoint) -> float: return abs(p.shuntImpedance()) diff --git a/src/NanoVNASaver/Charts/Permeability.py b/src/NanoVNASaver/Charts/Permeability.py index d8cbc4c..025e8a8 100644 --- a/src/NanoVNASaver/Charts/Permeability.py +++ b/src/NanoVNASaver/Charts/Permeability.py @@ -27,6 +27,7 @@ from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.SITools import Format, Value from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Charts.Frequency import FrequencyChart + logger = logging.getLogger(__name__) @@ -50,19 +51,26 @@ class PermeabilityChart(FrequencyChart): def drawChart(self, qp: QtGui.QPainter): qp.setPen(QtGui.QPen(Chart.color.text)) - qp.drawText(self.leftMargin + 5, 15, self.name + - " (\N{MICRO SIGN}\N{OHM SIGN} / Hz)") + qp.drawText( + self.leftMargin + 5, + 15, + self.name + " (\N{MICRO SIGN}\N{OHM SIGN} / Hz)", + ) qp.drawText(10, 15, "R") qp.drawText(self.leftMargin + self.dim.width + 10, 15, "X") qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin, - self.topMargin - 5, - self.leftMargin, - self.topMargin + self.dim.height + 5) - qp.drawLine(self.leftMargin - 5, - self.topMargin + self.dim.height, - self.leftMargin + self.dim.width + 5, - self.topMargin + self.dim.height) + qp.drawLine( + self.leftMargin, + self.topMargin - 5, + self.leftMargin, + self.topMargin + self.dim.height + 5, + ) + qp.drawLine( + self.leftMargin - 5, + self.topMargin + self.dim.height, + self.leftMargin + self.dim.width + 5, + self.topMargin + self.dim.height, + ) self.drawTitle(qp) def drawValues(self, qp: QtGui.QPainter): @@ -121,15 +129,16 @@ class PermeabilityChart(FrequencyChart): for i in range(horizontal_ticks): y = self.topMargin + round(i * self.dim.height / horizontal_ticks) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width + 5, y) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width + 5, y + ) qp.setPen(QtGui.QPen(Chart.color.text)) val = Value(self.valueAtPosition(y)[0], fmt=fmt) qp.drawText(3, y + 4, str(val)) - qp.drawText(3, - self.dim.height + self.topMargin, - str(Value(min_val, fmt=fmt))) + qp.drawText( + 3, self.dim.height + self.topMargin, str(Value(min_val, fmt=fmt)) + ) self.drawFrequencyTicks(qp) @@ -147,8 +156,11 @@ class PermeabilityChart(FrequencyChart): pen.setColor(c) qp.setPen(pen) qp.drawLine( - self.leftMargin + self.dim.width, 9, - self.leftMargin + self.dim.width + 5, 9) + self.leftMargin + self.dim.width, + 9, + self.leftMargin + self.dim.width + 5, + 9, + ) primary_pen.setWidth(self.dim.point) secondary_pen.setWidth(self.dim.point) @@ -177,7 +189,8 @@ class PermeabilityChart(FrequencyChart): qp.drawLine(x, y_re, prev_x, prev_y_re) else: new_x, new_y = self.getPlotable( - x, y_re, prev_x, prev_y_re) + x, y_re, prev_x, prev_y_re + ) qp.drawLine(x, y_re, new_x, new_y) elif self.isPlotable(prev_x, prev_y_re): new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re) @@ -191,7 +204,8 @@ class PermeabilityChart(FrequencyChart): qp.drawLine(x, y_im, prev_x, prev_y_im) else: new_x, new_y = self.getPlotable( - x, y_im, prev_x, prev_y_im) + x, y_im, prev_x, prev_y_im + ) qp.drawLine(x, y_im, new_x, new_y) elif self.isPlotable(prev_x, prev_y_im): new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im) @@ -213,8 +227,12 @@ class PermeabilityChart(FrequencyChart): pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) - qp.drawLine(self.leftMargin + self.dim.width, 14, - self.leftMargin + self.dim.width + 5, 14) + qp.drawLine( + self.leftMargin + self.dim.width, + 14, + self.leftMargin + self.dim.width + 5, + 14, + ) for i, reference in enumerate(self.reference): if reference.freq < self.fstart or reference.freq > self.fstop: @@ -241,7 +259,8 @@ class PermeabilityChart(FrequencyChart): qp.drawLine(x, y_re, prev_x, prev_y_re) else: new_x, new_y = self.getPlotable( - x, y_re, prev_x, prev_y_re) + x, y_re, prev_x, prev_y_re + ) qp.drawLine(x, y_re, new_x, new_y) elif self.isPlotable(prev_x, prev_y_re): new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re) @@ -255,7 +274,8 @@ class PermeabilityChart(FrequencyChart): qp.drawLine(x, y_im, prev_x, prev_y_im) else: new_x, new_y = self.getPlotable( - x, y_im, prev_x, prev_y_im) + x, y_im, prev_x, prev_y_im + ) qp.drawLine(x, y_im, new_x, new_y) elif self.isPlotable(prev_x, prev_y_im): new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im) @@ -268,10 +288,8 @@ class PermeabilityChart(FrequencyChart): y_re = self.getReYPosition(self.data[m.location]) y_im = self.getImYPosition(self.data[m.location]) - self.drawMarker(x, y_re, qp, m.color, - self.markers.index(m) + 1) - self.drawMarker(x, y_im, qp, m.color, - self.markers.index(m) + 1) + self.drawMarker(x, y_re, qp, m.color, self.markers.index(m) + 1) + self.drawMarker(x, y_im, qp, m.color, self.markers.index(m) + 1) def getImYPosition(self, d: Datapoint) -> int: im = d.impedance().imag @@ -283,10 +301,12 @@ class PermeabilityChart(FrequencyChart): else: return -1 return int( - self.topMargin + (math.log(self.max) - math.log(im)) / - span * self.dim.height) - return int(self.topMargin + (self.max - im) / - self.span * self.dim.height) + self.topMargin + + (math.log(self.max) - math.log(im)) / span * self.dim.height + ) + return int( + self.topMargin + (self.max - im) / self.span * self.dim.height + ) def getReYPosition(self, d: Datapoint) -> int: re = d.impedance().real @@ -298,10 +318,12 @@ class PermeabilityChart(FrequencyChart): else: return -1 return int( - self.topMargin + (math.log(self.max) - math.log(re)) / - span * self.dim.height) + self.topMargin + + (math.log(self.max) - math.log(re)) / span * self.dim.height + ) return int( - self.topMargin + (self.max - re) / self.span * self.dim.height) + self.topMargin + (self.max - re) / self.span * self.dim.height + ) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin diff --git a/src/NanoVNASaver/Charts/Phase.py b/src/NanoVNASaver/Charts/Phase.py index 582afc1..6f93c35 100644 --- a/src/NanoVNASaver/Charts/Phase.py +++ b/src/NanoVNASaver/Charts/Phase.py @@ -50,7 +50,8 @@ class PhaseChart(FrequencyChart): self.action_unwrap = QtWidgets.QAction("Unwrap") self.action_unwrap.setCheckable(True) self.action_unwrap.triggered.connect( - lambda: self.setUnwrap(self.action_unwrap.isChecked())) + lambda: self.setUnwrap(self.action_unwrap.isChecked()) + ) self.y_menu.addAction(self.action_unwrap) def copy(self): @@ -98,24 +99,32 @@ class PhaseChart(FrequencyChart): for i in range(tickcount): angle = minAngle + span * i / tickcount y = self.topMargin + int( - (self.maxAngle - angle) / self.span * self.dim.height) + (self.maxAngle - angle) / self.span * self.dim.height + ) if angle not in [minAngle, maxAngle]: qp.setPen(QtGui.QPen(Chart.color.text)) if angle != 0: digits = max( - 0, min(2, math.floor(3 - math.log10(abs(angle))))) - anglestr = str(round(angle)) if digits == 0 else str( - round(angle, digits)) + 0, min(2, math.floor(3 - math.log10(abs(angle)))) + ) + anglestr = ( + str(round(angle)) + if digits == 0 + else str(round(angle, digits)) + ) else: anglestr = "0" qp.drawText(3, y + 3, f"{anglestr}°") qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width, y) - qp.drawLine(self.leftMargin - 5, - self.topMargin, - self.leftMargin + self.dim.width, - self.topMargin) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width, y + ) + qp.drawLine( + self.leftMargin - 5, + self.topMargin, + self.leftMargin + self.dim.width, + self.topMargin, + ) qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 5, f"{maxAngle}°") qp.drawText(3, self.dim.height + self.topMargin, f"{minAngle}°") @@ -139,7 +148,8 @@ class PhaseChart(FrequencyChart): else: angle = math.degrees(d.phase) return self.topMargin + int( - (self.maxAngle - angle) / self.span * self.dim.height) + (self.maxAngle - angle) / self.span * self.dim.height + ) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin diff --git a/src/NanoVNASaver/Charts/Polar.py b/src/NanoVNASaver/Charts/Polar.py index 4d863f4..a3d9f0d 100644 --- a/src/NanoVNASaver/Charts/Polar.py +++ b/src/NanoVNASaver/Charts/Polar.py @@ -39,16 +39,25 @@ class PolarChart(SquareChart): qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawEllipse(QtCore.QPoint(center_x, center_y), width_2, height_2) - qp.drawEllipse(QtCore.QPoint(center_x, center_y), - width_2 // 2, height_2 // 2) + qp.drawEllipse( + QtCore.QPoint(center_x, center_y), width_2 // 2, height_2 // 2 + ) - qp.drawLine(center_x - width_2, center_y, - center_x + width_2, center_y) - qp.drawLine(center_x, center_y - height_2, - center_x, center_y + height_2) - qp.drawLine(center_x + width_45, center_y + height_45, - center_x - width_45, center_y - height_45) - qp.drawLine(center_x + width_45, center_y - height_45, - center_x - width_45, center_y + height_45) + qp.drawLine(center_x - width_2, center_y, center_x + width_2, center_y) + qp.drawLine( + center_x, center_y - height_2, center_x, center_y + height_2 + ) + qp.drawLine( + center_x + width_45, + center_y + height_45, + center_x - width_45, + center_y - height_45, + ) + qp.drawLine( + center_x + width_45, + center_y - height_45, + center_x - width_45, + center_y + height_45, + ) self.drawTitle(qp) diff --git a/src/NanoVNASaver/Charts/QFactor.py b/src/NanoVNASaver/Charts/QFactor.py index b8e620e..be14ab5 100644 --- a/src/NanoVNASaver/Charts/QFactor.py +++ b/src/NanoVNASaver/Charts/QFactor.py @@ -57,7 +57,7 @@ class QualityFactorChart(FrequencyChart): scale = 0 if maxQ > 0: scale = max(scale, math.floor(math.log10(maxQ))) - maxQ = math.ceil(maxQ / 10 ** scale) * 10 ** scale + maxQ = math.ceil(maxQ / 10**scale) * 10**scale self.minQ = self.minDisplayValue self.maxQ = maxQ @@ -69,8 +69,9 @@ class QualityFactorChart(FrequencyChart): for i in range(tickcount): q = self.minQ + i * self.span / tickcount - y = self.topMargin + int((self.maxQ - q) / self.span * - self.dim.height) + y = self.topMargin + int( + (self.maxQ - q) / self.span * self.dim.height + ) q = round(q) if q < 10: q = round(q, 2) @@ -79,12 +80,15 @@ class QualityFactorChart(FrequencyChart): qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(3, y + 3, str(q)) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width, y) - qp.drawLine(self.leftMargin - 5, - self.topMargin, - self.leftMargin + self.dim.width, - self.topMargin) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width, y + ) + qp.drawLine( + self.leftMargin - 5, + self.topMargin, + self.leftMargin + self.dim.width, + self.topMargin, + ) qp.setPen(Chart.color.text) max_q = round(maxQ) @@ -119,8 +123,9 @@ class QualityFactorChart(FrequencyChart): def getYPosition(self, d: Datapoint) -> int: Q = d.qFactor() - return self.topMargin + int((self.maxQ - Q) / self.span * - self.dim.height) + return self.topMargin + int( + (self.maxQ - Q) / self.span * self.dim.height + ) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin diff --git a/src/NanoVNASaver/Charts/RI.py b/src/NanoVNASaver/Charts/RI.py index 0d65b4a..9c875c3 100644 --- a/src/NanoVNASaver/Charts/RI.py +++ b/src/NanoVNASaver/Charts/RI.py @@ -62,11 +62,13 @@ class RealImaginaryChart(FrequencyChart): self.y_action_automatic.setCheckable(True) self.y_action_automatic.setChecked(True) self.y_action_automatic.changed.connect( - lambda: self.setFixedValues(self.y_action_fixed_span.isChecked())) + lambda: self.setFixedValues(self.y_action_fixed_span.isChecked()) + ) self.y_action_fixed_span = QtWidgets.QAction("Fixed span") self.y_action_fixed_span.setCheckable(True) self.y_action_fixed_span.changed.connect( - lambda: self.setFixedValues(self.y_action_fixed_span.isChecked())) + lambda: self.setFixedValues(self.y_action_fixed_span.isChecked()) + ) mode_group = QtWidgets.QActionGroup(self) mode_group.addAction(self.y_action_automatic) mode_group.addAction(self.y_action_fixed_span) @@ -110,11 +112,14 @@ class RealImaginaryChart(FrequencyChart): self.drawHorizontalTicks(qp) fmt = Format(max_nr_digits=3) - qp.drawText(3, self.dim.height + self.topMargin, - str(Value(min_real, fmt=fmt))) - qp.drawText(self.leftMargin + self.dim.width + 8, - self.dim.height + self.topMargin, - str(Value(min_imag, fmt=fmt))) + qp.drawText( + 3, self.dim.height + self.topMargin, str(Value(min_real, fmt=fmt)) + ) + qp.drawText( + self.leftMargin + self.dim.width + 8, + self.dim.height + self.topMargin, + str(Value(min_imag, fmt=fmt)), + ) self.drawFrequencyTicks(qp) @@ -131,8 +136,12 @@ class RealImaginaryChart(FrequencyChart): c.setAlpha(255) pen.setColor(c) qp.setPen(pen) - qp.drawLine(self.leftMargin + self.dim.width, 9, - self.leftMargin + self.dim.width + 5, 9) + qp.drawLine( + self.leftMargin + self.dim.width, + 9, + self.leftMargin + self.dim.width + 5, + 9, + ) primary_pen.setWidth(self.dim.point) secondary_pen.setWidth(self.dim.point) @@ -161,7 +170,8 @@ class RealImaginaryChart(FrequencyChart): qp.drawLine(x, y_re, prev_x, prev_y_re) else: new_x, new_y = self.getPlotable( - x, y_re, prev_x, prev_y_re) + x, y_re, prev_x, prev_y_re + ) qp.drawLine(x, y_re, new_x, new_y) elif self.isPlotable(prev_x, prev_y_re): new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re) @@ -175,7 +185,8 @@ class RealImaginaryChart(FrequencyChart): qp.drawLine(x, y_im, prev_x, prev_y_im) else: new_x, new_y = self.getPlotable( - x, y_im, prev_x, prev_y_im) + x, y_im, prev_x, prev_y_im + ) qp.drawLine(x, y_im, new_x, new_y) elif self.isPlotable(prev_x, prev_y_im): new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im) @@ -197,8 +208,12 @@ class RealImaginaryChart(FrequencyChart): pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) - qp.drawLine(self.leftMargin + self.dim.width, 14, - self.leftMargin + self.dim.width + 5, 14) + qp.drawLine( + self.leftMargin + self.dim.width, + 14, + self.leftMargin + self.dim.width + 5, + 14, + ) for i, reference in enumerate(self.reference): if reference.freq < self.fstart or reference.freq > self.fstop: @@ -225,7 +240,8 @@ class RealImaginaryChart(FrequencyChart): qp.drawLine(x, y_re, prev_x, prev_y_re) else: new_x, new_y = self.getPlotable( - x, y_re, prev_x, prev_y_re) + x, y_re, prev_x, prev_y_re + ) qp.drawLine(x, y_re, new_x, new_y) elif self.isPlotable(prev_x, prev_y_re): new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re) @@ -239,7 +255,8 @@ class RealImaginaryChart(FrequencyChart): qp.drawLine(x, y_im, prev_x, prev_y_im) else: new_x, new_y = self.getPlotable( - x, y_im, prev_x, prev_y_im) + x, y_im, prev_x, prev_y_im + ) qp.drawLine(x, y_im, new_x, new_y) elif self.isPlotable(prev_x, prev_y_im): new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im) @@ -252,10 +269,8 @@ class RealImaginaryChart(FrequencyChart): y_re = self.getReYPosition(self.data[m.location]) y_im = self.getImYPosition(self.data[m.location]) - self.drawMarker(x, y_re, qp, m.color, - self.markers.index(m) + 1) - self.drawMarker(x, y_im, qp, m.color, - self.markers.index(m) + 1) + self.drawMarker(x, y_re, qp, m.color, self.markers.index(m) + 1) + self.drawMarker(x, y_im, qp, m.color, self.markers.index(m) + 1) def drawHorizontalTicks(self, qp): # We want one horizontal tick per 50 pixels, at most @@ -264,8 +279,9 @@ class RealImaginaryChart(FrequencyChart): for i in range(horizontal_ticks): y = self.topMargin + i * self.dim.height // horizontal_ticks qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width + 5, y) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width + 5, y + ) qp.setPen(QtGui.QPen(Chart.color.text)) re = self.max_real - i * self.span_real / horizontal_ticks im = self.max_imag - i * self.span_imag / horizontal_ticks @@ -273,7 +289,8 @@ class RealImaginaryChart(FrequencyChart): qp.drawText( self.leftMargin + self.dim.width + 8, y + 4, - f"{Value(im, fmt=fmt)}") + f"{Value(im, fmt=fmt)}", + ) def find_scaling(self): # Find scaling @@ -350,20 +367,24 @@ class RealImaginaryChart(FrequencyChart): def getImYPosition(self, d: Datapoint) -> int: im = self.value(d).imag - return int(self.topMargin + (self.max_imag - im) / self.span_imag - * self.dim.height) + return int( + self.topMargin + + (self.max_imag - im) / self.span_imag * self.dim.height + ) def getReYPosition(self, d: Datapoint) -> int: re = self.value(d).real - return int(self.topMargin + (self.max_real - re) / self.span_real - * self.dim.height if math.isfinite(re) else self.topMargin) + return int( + self.topMargin + + (self.max_real - re) / self.span_real * self.dim.height + if math.isfinite(re) + else self.topMargin + ) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin - valRe = -1 * ((absy / self.dim.height * - self.span_real) - self.max_real) - valIm = -1 * ((absy / self.dim.height * - self.span_imag) - self.max_imag) + valRe = -1 * ((absy / self.dim.height * self.span_real) - self.max_real) + valIm = -1 * ((absy / self.dim.height * self.span_imag) - self.max_imag) return [valRe, valIm] def zoomTo(self, x1, y1, x2, y2): @@ -406,9 +427,12 @@ class RealImaginaryChart(FrequencyChart): def setMinimumRealValue(self): min_val, selected = QtWidgets.QInputDialog.getDouble( - self, "Minimum real value", - "Set minimum real value", value=self.minDisplayReal, - decimals=2) + self, + "Minimum real value", + "Set minimum real value", + value=self.minDisplayReal, + decimals=2, + ) if not selected: return if not (self.fixedValues and min_val >= self.maxDisplayReal): @@ -418,9 +442,12 @@ class RealImaginaryChart(FrequencyChart): def setMaximumRealValue(self): max_val, selected = QtWidgets.QInputDialog.getDouble( - self, "Maximum real value", - "Set maximum real value", value=self.maxDisplayReal, - decimals=2) + self, + "Maximum real value", + "Set maximum real value", + value=self.maxDisplayReal, + decimals=2, + ) if not selected: return if not (self.fixedValues and max_val <= self.minDisplayReal): @@ -430,9 +457,12 @@ class RealImaginaryChart(FrequencyChart): def setMinimumImagValue(self): min_val, selected = QtWidgets.QInputDialog.getDouble( - self, "Minimum imaginary value", - "Set minimum imaginary value", value=self.minDisplayImag, - decimals=2) + self, + "Minimum imaginary value", + "Set minimum imaginary value", + value=self.minDisplayImag, + decimals=2, + ) if not selected: return if not (self.fixedValues and min_val >= self.maxDisplayImag): @@ -442,9 +472,12 @@ class RealImaginaryChart(FrequencyChart): def setMaximumImagValue(self): max_val, selected = QtWidgets.QInputDialog.getDouble( - self, "Maximum imaginary value", - "Set maximum imaginary value", value=self.maxDisplayImag, - decimals=2) + self, + "Maximum imaginary value", + "Set maximum imaginary value", + value=self.maxDisplayImag, + decimals=2, + ) if not selected: return if not (self.fixedValues and max_val <= self.minDisplayImag): @@ -454,9 +487,10 @@ class RealImaginaryChart(FrequencyChart): def setFixedValues(self, fixed_values: bool): self.fixedValues = fixed_values - if (fixed_values and - (self.minDisplayReal >= self.maxDisplayReal or - self.minDisplayImag > self.maxDisplayImag)): + if fixed_values and ( + self.minDisplayReal >= self.maxDisplayReal + or self.minDisplayImag > self.maxDisplayImag + ): self.fixedValues = False self.y_action_automatic.setChecked(True) self.y_action_fixed_span.setChecked(False) @@ -464,17 +498,23 @@ class RealImaginaryChart(FrequencyChart): def contextMenuEvent(self, event): self.action_set_fixed_start.setText( - f"Start ({format_frequency_chart(self.minFrequency)})") + f"Start ({format_frequency_chart(self.minFrequency)})" + ) self.action_set_fixed_stop.setText( - f"Stop ({format_frequency_chart(self.maxFrequency)})") + f"Stop ({format_frequency_chart(self.maxFrequency)})" + ) self.action_set_fixed_minimum_real.setText( - f"Minimum R ({self.minDisplayReal})") + f"Minimum R ({self.minDisplayReal})" + ) self.action_set_fixed_maximum_real.setText( - f"Maximum R ({self.maxDisplayReal})") + f"Maximum R ({self.maxDisplayReal})" + ) self.action_set_fixed_minimum_imag.setText( - f"Minimum jX ({self.minDisplayImag})") + f"Minimum jX ({self.minDisplayImag})" + ) self.action_set_fixed_maximum_imag.setText( - f"Maximum jX ({self.maxDisplayImag})") + f"Maximum jX ({self.maxDisplayImag})" + ) self.menu.exec_(event.globalPos()) def value(self, p: Datapoint) -> complex: diff --git a/src/NanoVNASaver/Charts/RIMu.py b/src/NanoVNASaver/Charts/RIMu.py index 7282a06..bdf88b8 100644 --- a/src/NanoVNASaver/Charts/RIMu.py +++ b/src/NanoVNASaver/Charts/RIMu.py @@ -34,30 +34,37 @@ MU = "\N{GREEK SMALL LETTER MU}" class RealImaginaryMuChart(RealImaginaryChart): - def __init__(self, name=""): super().__init__(name) self.y_menu.addSeparator() self.action_set_fixed_maximum_real = QtWidgets.QAction( - f"Maximum {MU}' ({self.maxDisplayReal})") + f"Maximum {MU}' ({self.maxDisplayReal})" + ) self.action_set_fixed_maximum_real.triggered.connect( - self.setMaximumRealValue) + self.setMaximumRealValue + ) self.action_set_fixed_minimum_real = QtWidgets.QAction( - f"Minimum {MU}' ({self.minDisplayReal})") + f"Minimum {MU}' ({self.minDisplayReal})" + ) self.action_set_fixed_minimum_real.triggered.connect( - self.setMinimumRealValue) + self.setMinimumRealValue + ) self.action_set_fixed_maximum_imag = QtWidgets.QAction( - f"Maximum {MU}'' ({self.maxDisplayImag})") + f"Maximum {MU}'' ({self.maxDisplayImag})" + ) self.action_set_fixed_maximum_imag.triggered.connect( - self.setMaximumImagValue) + self.setMaximumImagValue + ) self.action_set_fixed_minimum_imag = QtWidgets.QAction( - f"Minimum {MU}'' ({self.minDisplayImag})") + f"Minimum {MU}'' ({self.minDisplayImag})" + ) self.action_set_fixed_minimum_imag.triggered.connect( - self.setMinimumImagValue) + self.setMinimumImagValue + ) self.y_menu.addAction(self.action_set_fixed_maximum_real) self.y_menu.addAction(self.action_set_fixed_minimum_real) @@ -67,25 +74,21 @@ class RealImaginaryMuChart(RealImaginaryChart): # Manage core parameters # TODO pick some sane default values? - self.coreLength = 1. - self.coreArea = 1. + self.coreLength = 1.0 + self.coreArea = 1.0 self.coreWindings = 1 self.menu.addSeparator() - self.action_set_core_length = QtWidgets.QAction( - "Core effective length") - self.action_set_core_length.triggered.connect( - self.setCoreLength) + self.action_set_core_length = QtWidgets.QAction("Core effective length") + self.action_set_core_length.triggered.connect(self.setCoreLength) - self.action_set_core_area = QtWidgets.QAction( - "Core area") - self.action_set_core_area.triggered.connect( - self.setCoreArea) + self.action_set_core_area = QtWidgets.QAction("Core area") + self.action_set_core_area.triggered.connect(self.setCoreArea) self.action_set_core_windings = QtWidgets.QAction( - "Core number of windings") - self.action_set_core_windings.triggered.connect( - self.setCoreWindings) + "Core number of windings" + ) + self.action_set_core_windings.triggered.connect(self.setCoreWindings) self.menu.addAction(self.action_set_core_length) self.menu.addAction(self.action_set_core_area) @@ -102,41 +105,53 @@ class RealImaginaryMuChart(RealImaginaryChart): def drawChart(self, qp: QtGui.QPainter): qp.setPen(QtGui.QPen(Chart.color.text)) - qp.drawText(self.leftMargin + 5, 15, - f"{self.name}") + qp.drawText(self.leftMargin + 5, 15, f"{self.name}") qp.drawText(5, 15, f"{MU}'") qp.drawText(self.leftMargin + self.dim.width + 10, 15, f"{MU}''") qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin, - self.topMargin - 5, - self.leftMargin, - self.topMargin + self.dim.height + 5) - qp.drawLine(self.leftMargin - 5, - self.topMargin + self.dim.height, - self.leftMargin + self.dim.width + 5, - self.topMargin + self.dim.height) + qp.drawLine( + self.leftMargin, + self.topMargin - 5, + self.leftMargin, + self.topMargin + self.dim.height + 5, + ) + qp.drawLine( + self.leftMargin - 5, + self.topMargin + self.dim.height, + self.leftMargin + self.dim.width + 5, + self.topMargin + self.dim.height, + ) self.drawTitle(qp) def contextMenuEvent(self, event): self.action_set_fixed_start.setText( - f"Start ({format_frequency_chart(self.minFrequency)})") + f"Start ({format_frequency_chart(self.minFrequency)})" + ) self.action_set_fixed_stop.setText( - f"Stop ({format_frequency_chart(self.maxFrequency)})") + f"Stop ({format_frequency_chart(self.maxFrequency)})" + ) self.action_set_fixed_minimum_real.setText( - f"Minimum {MU}' ({self.minDisplayReal})") + f"Minimum {MU}' ({self.minDisplayReal})" + ) self.action_set_fixed_maximum_real.setText( - f"Maximum {MU}' ({self.maxDisplayReal})") + f"Maximum {MU}' ({self.maxDisplayReal})" + ) self.action_set_fixed_minimum_imag.setText( - f"Minimum {MU}'' ({self.minDisplayImag})") + f"Minimum {MU}'' ({self.minDisplayImag})" + ) self.action_set_fixed_maximum_imag.setText( - f"Maximum {MU}'' ({self.maxDisplayImag})") + f"Maximum {MU}'' ({self.maxDisplayImag})" + ) self.menu.exec_(event.globalPos()) def setCoreLength(self): val, selected = QtWidgets.QInputDialog.getDouble( - self, "Core effective length", - "Set core effective length in mm", value=self.coreLength, - decimals=2) + self, + "Core effective length", + "Set core effective length in mm", + value=self.coreLength, + decimals=2, + ) if not selected: return if not (self.fixedValues and val >= 0): @@ -146,9 +161,12 @@ class RealImaginaryMuChart(RealImaginaryChart): def setCoreArea(self): val, selected = QtWidgets.QInputDialog.getDouble( - self, "Core effective area", + self, + "Core effective area", "Set core cross section area length in mm\N{SUPERSCRIPT TWO}", - value=self.coreArea, decimals=2) + value=self.coreArea, + decimals=2, + ) if not selected: return if not (self.fixedValues and val >= 0): @@ -158,8 +176,11 @@ class RealImaginaryMuChart(RealImaginaryChart): def setCoreWindings(self): val, selected = QtWidgets.QInputDialog.getInt( - self, "Core number of windings", - "Set core number of windings", value=self.coreWindings) + self, + "Core number of windings", + "Set core number of windings", + value=self.coreWindings, + ) if not selected: return if not (self.fixedValues and val >= 0): @@ -176,6 +197,7 @@ class RealImaginaryMuChart(RealImaginaryChart): # Core length and core area are in mm and mm2 respectively # note: mu_r = mu' - j * mu '' return np.conj( - inductance * (self.coreLength / 1e3) / - (mu_0 * self.coreWindings**2 * (self.coreArea / 1e6)) + inductance + * (self.coreLength / 1e3) + / (mu_0 * self.coreWindings**2 * (self.coreArea / 1e6)) ) diff --git a/src/NanoVNASaver/Charts/RIZ.py b/src/NanoVNASaver/Charts/RIZ.py index ecc9894..42c3be9 100644 --- a/src/NanoVNASaver/Charts/RIZ.py +++ b/src/NanoVNASaver/Charts/RIZ.py @@ -35,24 +35,32 @@ class RealImaginaryZChart(RealImaginaryChart): self.y_menu.addSeparator() self.action_set_fixed_maximum_real = QtWidgets.QAction( - f"Maximum R ({self.maxDisplayReal})") + f"Maximum R ({self.maxDisplayReal})" + ) self.action_set_fixed_maximum_real.triggered.connect( - self.setMaximumRealValue) + self.setMaximumRealValue + ) self.action_set_fixed_minimum_real = QtWidgets.QAction( - f"Minimum R ({self.minDisplayReal})") + f"Minimum R ({self.minDisplayReal})" + ) self.action_set_fixed_minimum_real.triggered.connect( - self.setMinimumRealValue) + self.setMinimumRealValue + ) self.action_set_fixed_maximum_imag = QtWidgets.QAction( - f"Maximum jX ({self.maxDisplayImag})") + f"Maximum jX ({self.maxDisplayImag})" + ) self.action_set_fixed_maximum_imag.triggered.connect( - self.setMaximumImagValue) + self.setMaximumImagValue + ) self.action_set_fixed_minimum_imag = QtWidgets.QAction( - f"Minimum jX ({self.minDisplayImag})") + f"Minimum jX ({self.minDisplayImag})" + ) self.action_set_fixed_minimum_imag.triggered.connect( - self.setMinimumImagValue) + self.setMinimumImagValue + ) self.y_menu.addAction(self.action_set_fixed_maximum_real) self.y_menu.addAction(self.action_set_fixed_minimum_real) @@ -62,34 +70,43 @@ class RealImaginaryZChart(RealImaginaryChart): def drawChart(self, qp: QtGui.QPainter): qp.setPen(QtGui.QPen(Chart.color.text)) - qp.drawText(self.leftMargin + 5, 15, - f"{self.name} (\N{OHM SIGN})") + qp.drawText(self.leftMargin + 5, 15, f"{self.name} (\N{OHM SIGN})") qp.drawText(10, 15, "R") qp.drawText(self.leftMargin + self.dim.width + 10, 15, "X") qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin, - self.topMargin - 5, - self.leftMargin, - self.topMargin + self.dim.height + 5) - qp.drawLine(self.leftMargin - 5, - self.topMargin + self.dim.height, - self.leftMargin + self.dim.width + 5, - self.topMargin + self.dim.height) + qp.drawLine( + self.leftMargin, + self.topMargin - 5, + self.leftMargin, + self.topMargin + self.dim.height + 5, + ) + qp.drawLine( + self.leftMargin - 5, + self.topMargin + self.dim.height, + self.leftMargin + self.dim.width + 5, + self.topMargin + self.dim.height, + ) self.drawTitle(qp) def contextMenuEvent(self, event): self.action_set_fixed_start.setText( - f"Start ({format_frequency_chart(self.minFrequency)})") + f"Start ({format_frequency_chart(self.minFrequency)})" + ) self.action_set_fixed_stop.setText( - f"Stop ({format_frequency_chart(self.maxFrequency)})") + f"Stop ({format_frequency_chart(self.maxFrequency)})" + ) self.action_set_fixed_minimum_real.setText( - f"Minimum R ({self.minDisplayReal})") + f"Minimum R ({self.minDisplayReal})" + ) self.action_set_fixed_maximum_real.setText( - f"Maximum R ({self.maxDisplayReal})") + f"Maximum R ({self.maxDisplayReal})" + ) self.action_set_fixed_minimum_imag.setText( - f"Minimum jX ({self.minDisplayImag})") + f"Minimum jX ({self.minDisplayImag})" + ) self.action_set_fixed_maximum_imag.setText( - f"Maximum jX ({self.maxDisplayImag})") + f"Maximum jX ({self.maxDisplayImag})" + ) self.menu.exec_(event.globalPos()) def value(self, p: Datapoint) -> complex: diff --git a/src/NanoVNASaver/Charts/RIZSeries.py b/src/NanoVNASaver/Charts/RIZSeries.py index 60f18fc..5d15ed7 100644 --- a/src/NanoVNASaver/Charts/RIZSeries.py +++ b/src/NanoVNASaver/Charts/RIZSeries.py @@ -25,6 +25,5 @@ logger = logging.getLogger(__name__) class RealImaginaryZSeriesChart(RealImaginaryZChart): - def impedance(self, p: Datapoint) -> complex: return p.seriesImpedance() diff --git a/src/NanoVNASaver/Charts/RIZShunt.py b/src/NanoVNASaver/Charts/RIZShunt.py index 1609282..387b8ab 100644 --- a/src/NanoVNASaver/Charts/RIZShunt.py +++ b/src/NanoVNASaver/Charts/RIZShunt.py @@ -25,6 +25,5 @@ logger = logging.getLogger(__name__) class RealImaginaryZShuntChart(RealImaginaryZChart): - def impedance(self, p: Datapoint) -> complex: return p.shuntImpedance() diff --git a/src/NanoVNASaver/Charts/SParam.py b/src/NanoVNASaver/Charts/SParam.py index 297af5d..30363b8 100644 --- a/src/NanoVNASaver/Charts/SParam.py +++ b/src/NanoVNASaver/Charts/SParam.py @@ -52,14 +52,18 @@ class SParameterChart(FrequencyChart): qp.drawText(10, 15, "Real") qp.drawText(self.leftMargin + self.dim.width - 15, 15, "Imag") qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin, - self.topMargin - 5, - self.leftMargin, - self.topMargin + self.dim.height + 5) - qp.drawLine(self.leftMargin - 5, - self.topMargin + self.dim.height, - self.leftMargin + self.dim.width, - self.topMargin + self.dim.height) + qp.drawLine( + self.leftMargin, + self.topMargin - 5, + self.leftMargin, + self.topMargin + self.dim.height + 5, + ) + qp.drawLine( + self.leftMargin - 5, + self.topMargin + self.dim.height, + self.leftMargin + self.dim.width, + self.topMargin + self.dim.height, + ) def drawValues(self, qp: QtGui.QPainter): if len(self.data) == 0 and len(self.reference) == 0: @@ -85,44 +89,58 @@ class SParameterChart(FrequencyChart): val = int(minValue + i * tick_step) y = self.topMargin + (maxValue - val) // span * self.dim.height qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width, y) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width, y + ) if val > minValue and val != maxValue: qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText(3, y + 4, str(round(val, 2))) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, self.topMargin, - self.leftMargin + self.dim.width, self.topMargin) + qp.drawLine( + self.leftMargin - 5, + self.topMargin, + self.leftMargin + self.dim.width, + self.topMargin, + ) qp.setPen(Chart.color.text) qp.drawText(3, self.topMargin + 4, f"{maxValue}") qp.drawText(3, self.dim.height + self.topMargin, f"{minValue}") self.drawFrequencyTicks(qp) self.drawData(qp, self.data, Chart.color.sweep, self.getReYPosition) - self.drawData(qp, self.reference, Chart.color.reference, - self.getReYPosition) - self.drawData(qp, self.data, Chart.color.sweep_secondary, - self.getImYPosition) - self.drawData(qp, self.reference, - Chart.color.reference_secondary, self.getImYPosition) + self.drawData( + qp, self.reference, Chart.color.reference, self.getReYPosition + ) + self.drawData( + qp, self.data, Chart.color.sweep_secondary, self.getImYPosition + ) + self.drawData( + qp, + self.reference, + Chart.color.reference_secondary, + self.getImYPosition, + ) self.drawMarkers(qp, y_function=self.getReYPosition) self.drawMarkers(qp, y_function=self.getImYPosition) def getYPosition(self, d: Datapoint) -> int: return int( - self.topMargin + (self.maxValue - d.re) / self.span * - self.dim.height) + self.topMargin + + (self.maxValue - d.re) / self.span * self.dim.height + ) def getReYPosition(self, d: Datapoint) -> int: return int( - self.topMargin + (self.maxValue - d.re) / self.span * - self.dim.height) + self.topMargin + + (self.maxValue - d.re) / self.span * self.dim.height + ) def getImYPosition(self, d: Datapoint) -> int: return int( - self.topMargin + (self.maxValue - d.im) / self.span * - self.dim.height) + self.topMargin + + (self.maxValue - d.im) / self.span * self.dim.height + ) def valueAtPosition(self, y) -> List[float]: absy = y - self.topMargin diff --git a/src/NanoVNASaver/Charts/Smith.py b/src/NanoVNASaver/Charts/Smith.py index 43b7c51..4009987 100644 --- a/src/NanoVNASaver/Charts/Smith.py +++ b/src/NanoVNASaver/Charts/Smith.py @@ -35,58 +35,119 @@ class SmithChart(SquareChart): qp.drawText(3, 15, self.name) qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawEllipse(QtCore.QPoint(center_x, center_y), width_2, height_2) - qp.drawLine(center_x - width_2, center_y, - center_x + width_2, center_y) + qp.drawLine(center_x - width_2, center_y, center_x + width_2, center_y) qp.drawEllipse( QtCore.QPoint(center_x + int(self.dim.width / 4), center_y), - self.dim.width // 4, self.dim.height // 4) # Re(Z) = 1 + self.dim.width // 4, + self.dim.height // 4, + ) # Re(Z) = 1 qp.drawEllipse( QtCore.QPoint(center_x + self.dim.width // 3, center_y), - self.dim.width // 6, self.dim.height // 6) # Re(Z) = 2 + self.dim.width // 6, + self.dim.height // 6, + ) # Re(Z) = 2 qp.drawEllipse( QtCore.QPoint(center_x + 3 * self.dim.width // 8, center_y), - self.dim.width // 8, self.dim.height // 8) # Re(Z) = 3 + self.dim.width // 8, + self.dim.height // 8, + ) # Re(Z) = 3 qp.drawEllipse( QtCore.QPoint(center_x + 5 * self.dim.width // 12, center_y), - self.dim.width // 12, self.dim.height // 12) # Re(Z) = 5 + self.dim.width // 12, + self.dim.height // 12, + ) # Re(Z) = 5 qp.drawEllipse( QtCore.QPoint(center_x + self.dim.width // 6, center_y), - self.dim.width // 3, self.dim.height // 3) # Re(Z) = 0.5 + self.dim.width // 3, + self.dim.height // 3, + ) # Re(Z) = 0.5 qp.drawEllipse( QtCore.QPoint(center_x + self.dim.width // 12, center_y), - 5 * self.dim.width // 12, 5 * self.dim.height // 12) # Re(Z) = 0.2 + 5 * self.dim.width // 12, + 5 * self.dim.height // 12, + ) # Re(Z) = 0.2 - qp.drawArc(center_x + 3 * self.dim.width // 8, center_y, - self.dim.width // 4, self.dim.width // 4, - 90 * 16, 152 * 16) # Im(Z) = -5 - qp.drawArc(center_x + 3 * self.dim.width // 8, center_y, - self.dim.width // 4, -self.dim.width // 4, - -90 * 16, -152 * 16) # Im(Z) = 5 - qp.drawArc(center_x + self.dim.width // 4, center_y, - width_2, height_2, - 90 * 16, 127 * 16) # Im(Z) = -2 - qp.drawArc(center_x + self.dim.width // 4, center_y, - width_2, -height_2, - -90 * 16, -127 * 16) # Im(Z) = 2 - qp.drawArc(center_x, center_y, - self.dim.width, self.dim.height, - 90 * 16, 90 * 16) # Im(Z) = -1 - qp.drawArc(center_x, center_y, - self.dim.width, - self.dim.height, - -90 * 16, -90 * 16) # Im(Z) = 1 - qp.drawArc(center_x - width_2, center_y, - self.dim.width * 2, self.dim.height * 2, - int(99.5 * 16), int(43.5 * 16)) # Im(Z) = -0.5 - qp.drawArc(center_x - width_2, center_y, - self.dim.width * 2, -self.dim.height * 2, - int(-99.5 * 16), int(-43.5 * 16)) # Im(Z) = 0.5 - qp.drawArc(center_x - self.dim.width * 2, center_y, - self.dim.width * 5, self.dim.height * 5, - int(93.85 * 16), int(18.85 * 16)) # Im(Z) = -0.2 - qp.drawArc(center_x - self.dim.width * 2, center_y, - self.dim.width * 5, -self.dim.height * 5, - int(-93.85 * 16), int(-18.85 * 16)) # Im(Z) = 0.2 + qp.drawArc( + center_x + 3 * self.dim.width // 8, + center_y, + self.dim.width // 4, + self.dim.width // 4, + 90 * 16, + 152 * 16, + ) # Im(Z) = -5 + qp.drawArc( + center_x + 3 * self.dim.width // 8, + center_y, + self.dim.width // 4, + -self.dim.width // 4, + -90 * 16, + -152 * 16, + ) # Im(Z) = 5 + qp.drawArc( + center_x + self.dim.width // 4, + center_y, + width_2, + height_2, + 90 * 16, + 127 * 16, + ) # Im(Z) = -2 + qp.drawArc( + center_x + self.dim.width // 4, + center_y, + width_2, + -height_2, + -90 * 16, + -127 * 16, + ) # Im(Z) = 2 + qp.drawArc( + center_x, + center_y, + self.dim.width, + self.dim.height, + 90 * 16, + 90 * 16, + ) # Im(Z) = -1 + qp.drawArc( + center_x, + center_y, + self.dim.width, + -self.dim.height, + -90 * 16, + -90 * 16, + ) # Im(Z) = 1 + qp.drawArc( + center_x - width_2, + center_y, + self.dim.width * 2, + self.dim.height * 2, + int(99.5 * 16), + int(43.5 * 16), + ) # Im(Z) = -0.5 + qp.drawArc( + center_x - width_2, + center_y, + self.dim.width * 2, + -self.dim.height * 2, + int(-99.5 * 16), + int(-43.5 * 16), + ) # Im(Z) = 0.5 + qp.drawArc( + center_x - self.dim.width * 2, + center_y, + self.dim.width * 5, + self.dim.height * 5, + int(93.85 * 16), + int(18.85 * 16), + ) # Im(Z) = -0.2 + qp.drawArc( + center_x - self.dim.width * 2, + center_y, + self.dim.width * 5, + -self.dim.height * 5, + int(-93.85 * 16), + int(-18.85 * 16), + ) # Im(Z) = 0.2 self.drawTitle(qp) @@ -99,4 +160,6 @@ class SmithChart(SquareChart): qp.drawEllipse(QtCore.QPoint(center_x, center_y), r, r) qp.drawText( QtCore.QRect(center_x - 50, center_y - 4 + r, 100, 20), - QtCore.Qt.AlignCenter, f"{swr}") + QtCore.Qt.AlignCenter, + f"{swr}", + ) diff --git a/src/NanoVNASaver/Charts/Square.py b/src/NanoVNASaver/Charts/Square.py index 2d01486..f654212 100644 --- a/src/NanoVNASaver/Charts/Square.py +++ b/src/NanoVNASaver/Charts/Square.py @@ -29,11 +29,11 @@ logger = logging.getLogger(__name__) class SquareChart(Chart): - def __init__(self, name=''): + def __init__(self, name=""): super().__init__(name) sizepolicy = QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Fixed, - QtWidgets.QSizePolicy.MinimumExpanding) + QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.MinimumExpanding + ) self.setSizePolicy(sizepolicy) self.dim.width = 250 self.dim.height = 250 @@ -53,8 +53,14 @@ class SquareChart(Chart): def drawChart(self, qp: QtGui.QPainter) -> None: raise NotImplementedError() - def draw_data(self, qp: QtGui.QPainter, color: QtGui.QColor, - data: List[Datapoint], fstart: int = 0, fstop: int = 0): + def draw_data( + self, + qp: QtGui.QPainter, + color: QtGui.QColor, + data: List[Datapoint], + fstart: int = 0, + fstop: int = 0, + ): if not data: return fstop = fstop or data[-1].freq @@ -65,8 +71,7 @@ class SquareChart(Chart): qp.setPen(pen) prev_x = self.getXPosition(data[0]) - prev_y = int(self.height() / 2 + data[0].im * -1 * - self.dim.height / 2) + prev_y = int(self.height() / 2 + data[0].im * -1 * self.dim.height / 2) for i, d in enumerate(data): x = self.getXPosition(d) y = int(self.height() / 2 + d.im * -1 * self.dim.height / 2) @@ -85,14 +90,15 @@ class SquareChart(Chart): fstart = self.data[0].freq if self.data else 0 fstop = self.data[-1].freq if self.data else 0 - self.draw_data(qp, Chart.color.reference, - self.reference, fstart, fstop) + self.draw_data(qp, Chart.color.reference, self.reference, fstart, fstop) for m in self.markers: if m.location != -1 and m.location < len(self.data): x = self.getXPosition(self.data[m.location]) - y = int(self.height() // 2 - - self.data[m.location].im * self.dim.height // 2) + y = int( + self.height() // 2 + - self.data[m.location].im * self.dim.height // 2 + ) self.drawMarker(x, y, qp, m.color, self.markers.index(m) + 1) def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: @@ -114,11 +120,13 @@ class SquareChart(Chart): y = a0.y() absx = x - (self.width() - self.dim.width) / 2 absy = y - (self.height() - self.dim.height) / 2 - if (absx < 0 or - absx > self.dim.width or - absy < 0 or - absy > self.dim.height or - (not self.data and not self.reference)): + if ( + absx < 0 + or absx > self.dim.width + or absy < 0 + or absy > self.dim.height + or (not self.data and not self.reference) + ): a0.ignore() return a0.accept() @@ -133,8 +141,9 @@ class SquareChart(Chart): positions = [ math.sqrt( - (x - (width_2 + d.re * dim_x_2))**2 + - (y - (height_2 - d.im * dim_y_2))**2) + (x - (width_2 + d.re * dim_x_2)) ** 2 + + (y - (height_2 - d.im * dim_y_2)) ** 2 + ) for d in target ] diff --git a/src/NanoVNASaver/Charts/TDR.py b/src/NanoVNASaver/Charts/TDR.py index 0530d1f..b0fe301 100644 --- a/src/NanoVNASaver/Charts/TDR.py +++ b/src/NanoVNASaver/Charts/TDR.py @@ -49,7 +49,9 @@ class TDRChart(Chart): self.setSizePolicy( QtWidgets.QSizePolicy( QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding)) + QtWidgets.QSizePolicy.MinimumExpanding, + ) + ) pal = QtGui.QPalette() pal.setColor(QtGui.QPalette.Background, Chart.color.background) self.setPalette(pal) @@ -68,11 +70,13 @@ class TDRChart(Chart): self.action_automatic.setCheckable(True) self.action_automatic.setChecked(True) self.action_automatic.changed.connect( - lambda: self.setFixedSpan(self.action_fixed_span.isChecked())) + lambda: self.setFixedSpan(self.action_fixed_span.isChecked()) + ) self.action_fixed_span = QtWidgets.QAction("Fixed span") self.action_fixed_span.setCheckable(True) self.action_fixed_span.changed.connect( - lambda: self.setFixedSpan(self.action_fixed_span.isChecked())) + lambda: self.setFixedSpan(self.action_fixed_span.isChecked()) + ) self.mode_group.addAction(self.action_automatic) self.mode_group.addAction(self.action_fixed_span) self.x_menu.addAction(self.action_automatic) @@ -80,11 +84,13 @@ class TDRChart(Chart): self.x_menu.addSeparator() self.action_set_fixed_start = QtWidgets.QAction( - f"Start ({self.minDisplayLength})") + f"Start ({self.minDisplayLength})" + ) self.action_set_fixed_start.triggered.connect(self.setMinimumLength) self.action_set_fixed_stop = QtWidgets.QAction( - f"Stop ({self.maxDisplayLength})") + f"Stop ({self.maxDisplayLength})" + ) self.action_set_fixed_stop.triggered.connect(self.setMaximumLength) self.x_menu.addAction(self.action_set_fixed_start) @@ -96,11 +102,13 @@ class TDRChart(Chart): self.y_action_automatic.setCheckable(True) self.y_action_automatic.setChecked(True) self.y_action_automatic.changed.connect( - lambda: self.setFixedValues(self.y_action_fixed.isChecked())) + lambda: self.setFixedValues(self.y_action_fixed.isChecked()) + ) self.y_action_fixed = QtWidgets.QAction("Fixed") self.y_action_fixed.setCheckable(True) self.y_action_fixed.changed.connect( - lambda: self.setFixedValues(self.y_action_fixed.isChecked())) + lambda: self.setFixedValues(self.y_action_fixed.isChecked()) + ) self.y_mode_group.addAction(self.y_action_automatic) self.y_mode_group.addAction(self.y_action_fixed) self.y_menu.addAction(self.y_action_automatic) @@ -108,14 +116,18 @@ class TDRChart(Chart): self.y_menu.addSeparator() self.y_action_set_fixed_maximum = QtWidgets.QAction( - f"Maximum ({self.maxImpedance})") + f"Maximum ({self.maxImpedance})" + ) self.y_action_set_fixed_maximum.triggered.connect( - self.setMaximumImpedance) + self.setMaximumImpedance + ) self.y_action_set_fixed_minimum = QtWidgets.QAction( - f"Minimum ({self.minImpedance})") + f"Minimum ({self.minImpedance})" + ) self.y_action_set_fixed_minimum.triggered.connect( - self.setMinimumImpedance) + self.setMinimumImpedance + ) self.y_menu.addAction(self.y_action_set_fixed_maximum) self.y_menu.addAction(self.y_action_set_fixed_minimum) @@ -126,26 +138,29 @@ class TDRChart(Chart): self.menu.addAction(self.action_save_screenshot) self.action_popout = QtWidgets.QAction("Popout chart") self.action_popout.triggered.connect( - lambda: self.popoutRequested.emit(self)) + lambda: self.popoutRequested.emit(self) + ) self.menu.addAction(self.action_popout) self.dim.width = self.width() - self.leftMargin - self.rightMargin self.dim.height = self.height() - self.bottomMargin - self.topMargin def contextMenuEvent(self, event): - self.action_set_fixed_start.setText( - f"Start ({self.minDisplayLength})") - self.action_set_fixed_stop.setText( - f"Stop ({self.maxDisplayLength})") + self.action_set_fixed_start.setText(f"Start ({self.minDisplayLength})") + self.action_set_fixed_stop.setText(f"Stop ({self.maxDisplayLength})") self.y_action_set_fixed_minimum.setText( - f"Minimum ({self.minImpedance})") + f"Minimum ({self.minImpedance})" + ) self.y_action_set_fixed_maximum.setText( - f"Maximum ({self.maxImpedance})") + f"Maximum ({self.maxImpedance})" + ) self.menu.exec_(event.globalPos()) def isPlotable(self, x, y): - return self.leftMargin <= x <= self.width() - self.rightMargin and \ - self.topMargin <= y <= self.height() - self.bottomMargin + return ( + self.leftMargin <= x <= self.width() - self.rightMargin + and self.topMargin <= y <= self.height() - self.bottomMargin + ) def resetDisplayLimits(self): self.fixedSpan = False @@ -162,9 +177,13 @@ class TDRChart(Chart): def setMinimumLength(self): min_val, selected = QtWidgets.QInputDialog.getDouble( - self, "Start length (m)", - "Set start length (m)", value=self.minDisplayLength, - min=0, decimals=1) + self, + "Start length (m)", + "Set start length (m)", + value=self.minDisplayLength, + min=0, + decimals=1, + ) if not selected: return if not (self.fixedSpan and min_val >= self.maxDisplayLength): @@ -174,9 +193,13 @@ class TDRChart(Chart): def setMaximumLength(self): max_val, selected = QtWidgets.QInputDialog.getDouble( - self, "Stop length (m)", - "Set stop length (m)", value=self.minDisplayLength, - min=0.1, decimals=1) + self, + "Stop length (m)", + "Set stop length (m)", + value=self.minDisplayLength, + min=0.1, + decimals=1, + ) if not selected: return if not (self.fixedSpan and max_val <= self.minDisplayLength): @@ -190,10 +213,13 @@ class TDRChart(Chart): def setMinimumImpedance(self): min_val, selected = QtWidgets.QInputDialog.getDouble( - self, "Minimum impedance (\N{OHM SIGN})", + self, + "Minimum impedance (\N{OHM SIGN})", "Set minimum impedance (\N{OHM SIGN})", value=self.minDisplayLength, - min=0, decimals=1) + min=0, + decimals=1, + ) if not selected: return if not (self.fixedValues and min_val >= self.maxImpedance): @@ -203,10 +229,13 @@ class TDRChart(Chart): def setMaximumImpedance(self): max_val, selected = QtWidgets.QInputDialog.getDouble( - self, "Maximum impedance (\N{OHM SIGN})", + self, + "Maximum impedance (\N{OHM SIGN})", "Set maximum impedance (\N{OHM SIGN})", value=self.minDisplayLength, - min=0.1, decimals=1) + min=0.1, + decimals=1, + ) if not selected: return if not (self.fixedValues and max_val <= self.minImpedance): @@ -236,9 +265,12 @@ class TDRChart(Chart): if self.dragbox.move_x != -1 and self.dragbox.move_y != -1: dx = self.dragbox.move_x - a0.x() dy = self.dragbox.move_y - a0.y() - self.zoomTo(self.leftMargin + dx, self.topMargin + dy, - self.leftMargin + self.dim.width + dx, - self.topMargin + self.dim.height + dy) + self.zoomTo( + self.leftMargin + dx, + self.topMargin + dy, + self.leftMargin + self.dim.width + dx, + self.topMargin + self.dim.height + dy, + ) self.dragbox.move_x = a0.x() self.dragbox.move_y = a0.y() return @@ -261,13 +293,14 @@ class TDRChart(Chart): if self.tdrWindow.td: if self.fixedSpan: max_index = np.searchsorted( - self.tdrWindow.distance_axis, self.maxDisplayLength * 2) + self.tdrWindow.distance_axis, self.maxDisplayLength * 2 + ) min_index = np.searchsorted( - self.tdrWindow.distance_axis, self.minDisplayLength * 2) + self.tdrWindow.distance_axis, self.minDisplayLength * 2 + ) x_step = (max_index - min_index) / width else: - max_index = math.ceil( - len(self.tdrWindow.distance_axis) / 2) + max_index = math.ceil(len(self.tdrWindow.distance_axis) / 2) x_step = max_index / width self.markerLocation = int(round(absx * x_step)) @@ -282,17 +315,21 @@ class TDRChart(Chart): qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.drawLine(x, self.topMargin, x, self.topMargin + height) qp.setPen(QtGui.QPen(Chart.color.text)) - distance = self.tdrWindow.distance_axis[ - min_index + - int((x - self.leftMargin) * x_step) - 1] / 2 - qp.drawText(x - 15, self.topMargin + height + 15, - f"{round(distance, 1)}m") + distance = ( + self.tdrWindow.distance_axis[ + min_index + int((x - self.leftMargin) * x_step) - 1 + ] + / 2 + ) + qp.drawText( + x - 15, self.topMargin + height + 15, f"{round(distance, 1)}m" + ) qp.setPen(QtGui.QPen(Chart.color.text)) qp.drawText( self.leftMargin - 10, self.topMargin + height + 15, - str(round(self.tdrWindow.distance_axis[min_index] / 2, - 1)) + "m") + str(round(self.tdrWindow.distance_axis[min_index] / 2, 1)) + "m", + ) def _draw_y_ticks(self, height, width, min_impedance, max_impedance): qp = QtGui.QPainter(self) @@ -308,7 +345,8 @@ class TDRChart(Chart): qp.drawText(3, y + 3, str(round(y_val, 1))) qp.setPen(Chart.color.text) qp.drawText( - 3, self.topMargin + height + 3, f"{round(min_impedance, 1)}") + 3, self.topMargin + height + 3, f"{round(min_impedance, 1)}" + ) def _draw_max_point(self, height, x_step, y_step, min_index): qp = QtGui.QPainter(self) @@ -316,22 +354,25 @@ class TDRChart(Chart): max_point = QtCore.QPoint( self.leftMargin + int((id_max - min_index) / x_step), - (self.topMargin + height) - int( - self.tdrWindow.td[id_max] / y_step)) + (self.topMargin + height) - int(self.tdrWindow.td[id_max] / y_step), + ) qp.setPen(self.markers[0].color) qp.drawEllipse(max_point, 2, 2) qp.setPen(Chart.color.text) - qp.drawText(max_point.x() - 10, max_point.y() - 5, - f"{round(self.tdrWindow.distance_axis[id_max] / 2, 2)}m") + qp.drawText( + max_point.x() - 10, + max_point.y() - 5, + f"{round(self.tdrWindow.distance_axis[id_max] / 2, 2)}m", + ) def _draw_marker(self, height, x_step, y_step, min_index): qp = QtGui.QPainter(self) marker_point = QtCore.QPoint( - self.leftMargin + - int((self.markerLocation - min_index) / x_step), - (self.topMargin + height) - - int(self.tdrWindow.td[self.markerLocation] / y_step)) + self.leftMargin + int((self.markerLocation - min_index) / x_step), + (self.topMargin + height) + - int(self.tdrWindow.td[self.markerLocation] / y_step), + ) qp.setPen(Chart.color.text) qp.drawEllipse(marker_point, 2, 2) qp.drawText( @@ -339,19 +380,21 @@ class TDRChart(Chart): marker_point.y() - 5, f"""{round( self.tdrWindow.distance_axis[self.markerLocation] / 2, - 2)}m""") + 2)}m""", + ) def _draw_graph(self, height, width): min_index = 0 - max_index = math.ceil( - len(self.tdrWindow.distance_axis) / 2) + max_index = math.ceil(len(self.tdrWindow.distance_axis) / 2) if self.fixedSpan: max_length = max(0.1, self.maxDisplayLength) max_index = np.searchsorted( - self.tdrWindow.distance_axis, max_length * 2) + self.tdrWindow.distance_axis, max_length * 2 + ) min_index = np.searchsorted( - self.tdrWindow.distance_axis, self.minDisplayLength * 2) + self.tdrWindow.distance_axis, self.minDisplayLength * 2 + ) if max_index == min_index: if max_index < len(self.tdrWindow.distance_axis) - 1: max_index += 1 @@ -361,8 +404,7 @@ class TDRChart(Chart): # TODO: Limit the search to the selected span? min_impedance = max(0, np.min(self.tdrWindow.step_response_Z) / 1.05) - max_impedance = min(1000, np.max( - self.tdrWindow.step_response_Z) * 1.05) + max_impedance = min(1000, np.max(self.tdrWindow.step_response_Z) * 1.05) if self.fixedValues: min_impedance = max(0, self.minImpedance) max_impedance = max(0.1, self.maxImpedance) @@ -370,7 +412,7 @@ class TDRChart(Chart): y_step = max(self.tdrWindow.td) * 1.1 / height or 1.0e-30 self._draw_ticks(height, width, x_step, min_index) - self._draw_y_ticks(height, width, min_impedance, max_impedance) + self._draw_y_ticks(height, width, min_impedance, max_impedance) qp = QtGui.QPainter(self) pen = QtGui.QPen(Chart.color.sweep) @@ -388,7 +430,8 @@ class TDRChart(Chart): x = self.leftMargin + int((i - min_index) / x_step) y = (self.topMargin + height) - int( - (self.tdrWindow.step_response_Z[i] - min_impedance) / y_step) + (self.tdrWindow.step_response_Z[i] - min_impedance) / y_step + ) if self.isPlotable(x, y): pen.setColor(Chart.color.sweep_secondary) qp.setPen(pen) @@ -408,14 +451,18 @@ class TDRChart(Chart): height = self.height() - self.bottomMargin - self.topMargin qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, - self.height() - self.bottomMargin, - self.width() - self.rightMargin, - self.height() - self.bottomMargin) - qp.drawLine(self.leftMargin, - self.topMargin - 5, - self.leftMargin, - self.height() - self.bottomMargin + 5) + qp.drawLine( + self.leftMargin - 5, + self.height() - self.bottomMargin, + self.width() - self.rightMargin, + self.height() - self.bottomMargin, + ) + qp.drawLine( + self.leftMargin, + self.topMargin - 5, + self.leftMargin, + self.height() - self.bottomMargin + 5, + ) # Number of ticks does not include the origin self.drawTitle(qp) @@ -424,12 +471,13 @@ class TDRChart(Chart): if self.dragbox.state and self.dragbox.pos[0] != -1: dashed_pen = QtGui.QPen( - Chart.color.foreground, 1, QtCore.Qt.DashLine) + Chart.color.foreground, 1, QtCore.Qt.DashLine + ) qp.setPen(dashed_pen) qp.drawRect( QtCore.QRect( QtCore.QPoint(*self.dragbox.pos_start), - QtCore.QPoint(*self.dragbox.pos) + QtCore.QPoint(*self.dragbox.pos), ) ) @@ -444,11 +492,11 @@ class TDRChart(Chart): max_impedance = self.maxImpedance else: min_impedance = max( - 0, - np.min(self.tdrWindow.step_response_Z) / 1.05) + 0, np.min(self.tdrWindow.step_response_Z) / 1.05 + ) max_impedance = min( - 1000, - np.max(self.tdrWindow.step_response_Z) * 1.05) + 1000, np.max(self.tdrWindow.step_response_Z) * 1.05 + ) y_step = (max_impedance - min_impedance) / height return y_step * absy + min_impedance return 0 @@ -459,20 +507,28 @@ class TDRChart(Chart): width = self.width() - self.leftMargin - self.rightMargin absx = x - self.leftMargin min_length = self.minDisplayLength if self.fixedSpan else 0 - max_length = self.maxDisplayLength if self.fixedSpan else ( - self.tdrWindow.distance_axis[ - math.ceil(len(self.tdrWindow.distance_axis) / 2) - ] / 2) + max_length = ( + self.maxDisplayLength + if self.fixedSpan + else ( + self.tdrWindow.distance_axis[ + math.ceil(len(self.tdrWindow.distance_axis) / 2) + ] + / 2 + ) + ) x_step = (max_length - min_length) / width if limit and absx < 0: return min_length - return (max_length if limit and absx > width else - absx * x_step + min_length) + return ( + max_length if limit and absx > width else absx * x_step + min_length + ) def zoomTo(self, x1, y1, x2, y2): logger.debug( - "Zoom to (x,y) by (x,y): (%d, %d) by (%d, %d)", x1, y1, x2, y2) + "Zoom to (x,y) by (x,y): (%d, %d) by (%d, %d)", x1, y1, x2, y2 + ) val1 = self.valueAtPosition(y1) val2 = self.valueAtPosition(y2) diff --git a/src/NanoVNASaver/Charts/VSWR.py b/src/NanoVNASaver/Charts/VSWR.py index cc962e4..cd3bf44 100644 --- a/src/NanoVNASaver/Charts/VSWR.py +++ b/src/NanoVNASaver/Charts/VSWR.py @@ -30,7 +30,6 @@ logger = logging.getLogger(__name__) class VSWRChart(FrequencyChart): - def __init__(self, name=""): super().__init__(name) @@ -90,19 +89,22 @@ class VSWRChart(FrequencyChart): qp.setPen(Chart.color.text) if vswr != 0: digits = max( - 0, min(2, math.floor(3 - math.log10(abs(vswr))))) + 0, min(2, math.floor(3 - math.log10(abs(vswr)))) + ) v_text = f"{round(vswr, digits)}" if digits else "0" qp.drawText(3, y + 3, v_text) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width, y) - qp.drawLine(self.leftMargin - 5, - self.topMargin + self.dim.height, - self.leftMargin + self.dim.width, - self.topMargin + self.dim.height) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width, y + ) + qp.drawLine( + self.leftMargin - 5, + self.topMargin + self.dim.height, + self.leftMargin + self.dim.width, + self.topMargin + self.dim.height, + ) qp.setPen(Chart.color.text) - digits = max( - 0, min(2, math.floor(3 - math.log10(abs(minVSWR))))) + digits = max(0, min(2, math.floor(3 - math.log10(abs(minVSWR))))) v_text = f"{round(minVSWR, digits)}" if digits else "0" qp.drawText(3, self.topMargin + self.dim.height, v_text) else: @@ -112,16 +114,20 @@ class VSWRChart(FrequencyChart): qp.setPen(Chart.color.text) if vswr != 0: digits = max( - 0, min(2, math.floor(3 - math.log10(abs(vswr))))) + 0, min(2, math.floor(3 - math.log10(abs(vswr)))) + ) vswrstr = f"{round(vswr, digits)}" if digits else "0" qp.drawText(3, y + 3, vswrstr) qp.setPen(QtGui.QPen(Chart.color.foreground)) - qp.drawLine(self.leftMargin - 5, y, - self.leftMargin + self.dim.width, y) - qp.drawLine(self.leftMargin - 5, - self.topMargin, - self.leftMargin + self.dim.width, - self.topMargin) + qp.drawLine( + self.leftMargin - 5, y, self.leftMargin + self.dim.width, y + ) + qp.drawLine( + self.leftMargin - 5, + self.topMargin, + self.leftMargin + self.dim.width, + self.topMargin, + ) qp.setPen(Chart.color.text) digits = max(0, min(2, math.floor(3 - math.log10(abs(maxVSWR))))) v_text = f"{round(maxVSWR, digits)}" if digits else "0" @@ -130,8 +136,7 @@ class VSWRChart(FrequencyChart): qp.setPen(Chart.color.swr) for vswr in self.swrMarkers: y = self.getYPositionFromValue(vswr) - qp.drawLine(self.leftMargin, y, - self.leftMargin + self.dim.width, y) + qp.drawLine(self.leftMargin, y, self.leftMargin + self.dim.width, y) qp.drawText(self.leftMargin + 3, y - 1, str(vswr)) self.drawFrequencyTicks(qp) @@ -146,13 +151,15 @@ class VSWRChart(FrequencyChart): span = math.log(self.maxVSWR) - math.log(min_val) else: return -1 - return ( - self.topMargin + int( - (math.log(self.maxVSWR) - math.log(vswr)) / - span * self.dim.height)) + return self.topMargin + int( + (math.log(self.maxVSWR) - math.log(vswr)) + / span + * self.dim.height + ) try: return self.topMargin + int( - (self.maxVSWR - vswr) / self.span * self.dim.height) + (self.maxVSWR - vswr) / self.span * self.dim.height + ) except OverflowError: return self.topMargin diff --git a/src/NanoVNASaver/Charts/__init__.py b/src/NanoVNASaver/Charts/__init__.py index 80076a3..10635a6 100644 --- a/src/NanoVNASaver/Charts/__init__.py +++ b/src/NanoVNASaver/Charts/__init__.py @@ -23,30 +23,31 @@ from .Smith import SmithChart from .SParam import SParameterChart from .TDR import TDRChart from .VSWR import VSWRChart + __all__ = [ - 'Chart', - 'FrequencyChart', - 'PolarChart', - 'SquareChart', - 'CapacitanceChart', - 'InductanceChart', - 'GroupDelayChart', - 'LogMagChart', - 'CombinedLogMagChart', - 'MagnitudeChart', - 'MagnitudeZChart', - 'MagnitudeZShuntChart', - 'MagnitudeZSeriesChart', - 'PermeabilityChart', - 'PhaseChart', - 'QualityFactorChart', - 'RealImaginaryChart', - 'RealImaginaryMuChart', - 'RealImaginaryZChart', - 'RealImaginaryZShuntChart', - 'RealImaginaryZSeriesChart', - 'SmithChart', - 'SParameterChart', - 'TDRChart', - 'VSWRChart', + "Chart", + "FrequencyChart", + "PolarChart", + "SquareChart", + "CapacitanceChart", + "InductanceChart", + "GroupDelayChart", + "LogMagChart", + "CombinedLogMagChart", + "MagnitudeChart", + "MagnitudeZChart", + "MagnitudeZShuntChart", + "MagnitudeZSeriesChart", + "PermeabilityChart", + "PhaseChart", + "QualityFactorChart", + "RealImaginaryChart", + "RealImaginaryMuChart", + "RealImaginaryZChart", + "RealImaginaryZShuntChart", + "RealImaginaryZSeriesChart", + "SmithChart", + "SParameterChart", + "TDRChart", + "VSWRChart", ] diff --git a/src/NanoVNASaver/Controls/MarkerControl.py b/src/NanoVNASaver/Controls/MarkerControl.py index 54d18f2..9172c5c 100644 --- a/src/NanoVNASaver/Controls/MarkerControl.py +++ b/src/NanoVNASaver/Controls/MarkerControl.py @@ -29,16 +29,16 @@ logger = logging.getLogger(__name__) class ShowButton(QtWidgets.QPushButton): - def setText(self, text: str = ''): + def setText(self, text: str = ""): if not text: - text = ("Show data" - if Defaults.cfg.gui.markers_hidden else "Hide data") + text = ( + "Show data" if Defaults.cfg.gui.markers_hidden else "Hide data" + ) super().setText(text) self.setToolTip("Toggle visibility of marker readings area") class MarkerControl(Control): - def __init__(self, app: QtWidgets.QWidget): super().__init__(app, "Markers") @@ -72,7 +72,8 @@ class MarkerControl(Control): lock_radiobutton = QtWidgets.QRadioButton("Locked") lock_radiobutton.setLayoutDirection(QtCore.Qt.RightToLeft) lock_radiobutton.setSizePolicy( - QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) + QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred + ) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(self.showMarkerButton) @@ -82,8 +83,7 @@ class MarkerControl(Control): def toggle_frame(self): def settings(hidden: bool): Defaults.cfg.gui.markers_hidden = not hidden - self.app.marker_frame.setHidden( - Defaults.cfg.gui.markers_hidden) + self.app.marker_frame.setHidden(Defaults.cfg.gui.markers_hidden) self.showMarkerButton.setText() self.showMarkerButton.repaint() diff --git a/src/NanoVNASaver/Controls/SerialControl.py b/src/NanoVNASaver/Controls/SerialControl.py index a861251..4113510 100644 --- a/src/NanoVNASaver/Controls/SerialControl.py +++ b/src/NanoVNASaver/Controls/SerialControl.py @@ -28,7 +28,6 @@ logger = logging.getLogger(__name__) class SerialControl(Control): - def __init__(self, app: QtWidgets.QWidget): super().__init__(app, "Serial port control") @@ -58,7 +57,8 @@ class SerialControl(Control): self.btn_settings.setMinimumHeight(20) self.btn_settings.setFixedWidth(60) self.btn_settings.clicked.connect( - lambda: self.app.display_window("device_settings")) + lambda: self.app.display_window("device_settings") + ) button_layout.addWidget(self.btn_settings, stretch=0) self.layout.addRow(button_layout) @@ -82,8 +82,9 @@ class SerialControl(Control): try: self.interface.open() except (IOError, AttributeError) as exc: - logger.error("Tried to open %s and failed: %s", - self.interface, exc) + logger.error( + "Tried to open %s and failed: %s", self.interface, exc + ) return if not self.interface.isOpen(): logger.error("Unable to open port %s", self.interface) @@ -96,7 +97,8 @@ class SerialControl(Control): logger.error("Unable to connect to VNA: %s", exc) self.app.vna.validateInput = self.app.settings.value( - "SerialInputValidation", True, bool) + "SerialInputValidation", True, bool + ) # connected self.btn_toggle.setText("Disconnect") @@ -106,16 +108,20 @@ class SerialControl(Control): if not frequencies: logger.warning("No frequencies read") return - logger.info("Read starting frequency %s and end frequency %s", - frequencies[0], frequencies[-1]) + logger.info( + "Read starting frequency %s and end frequency %s", + frequencies[0], + frequencies[-1], + ) self.app.sweep_control.set_start(frequencies[0]) if frequencies[0] < frequencies[-1]: self.app.sweep_control.set_end(frequencies[-1]) else: self.app.sweep_control.set_end( - frequencies[0] + - self.app.vna.datapoints * - self.app.sweep_control.get_segments()) + frequencies[0] + + self.app.vna.datapoints + * self.app.sweep_control.get_segments() + ) self.app.sweep_control.set_segments(1) # speed up things self.app.sweep_control.update_center_span() diff --git a/src/NanoVNASaver/Controls/SweepControl.py b/src/NanoVNASaver/Controls/SweepControl.py index 959e215..b32a969 100644 --- a/src/NanoVNASaver/Controls/SweepControl.py +++ b/src/NanoVNASaver/Controls/SweepControl.py @@ -21,8 +21,10 @@ import logging from PyQt5 import QtWidgets, QtCore from NanoVNASaver.Formatting import ( - format_frequency_sweep, format_frequency_short, - parse_frequency) + format_frequency_sweep, + format_frequency_short, + parse_frequency, +) from NanoVNASaver.Inputs import FrequencyInputWidget from NanoVNASaver.Controls.Control import Control @@ -30,7 +32,6 @@ logger = logging.getLogger(__name__) class SweepControl(Control): - def __init__(self, app: QtWidgets.QWidget): super().__init__(app, "Sweep control") @@ -66,8 +67,7 @@ class SweepControl(Control): self.input_center.setAlignment(QtCore.Qt.AlignRight) self.input_center.textEdited.connect(self.update_start_end) - input_right_layout.addRow(QtWidgets.QLabel( - "Center"), self.input_center) + input_right_layout.addRow(QtWidgets.QLabel("Center"), self.input_center) self.input_span = FrequencyInputWidget() self.input_span.setFixedHeight(20) @@ -77,7 +77,8 @@ class SweepControl(Control): input_right_layout.addRow(QtWidgets.QLabel("Span"), self.input_span) self.input_segments = QtWidgets.QLineEdit( - self.app.settings.value("Segments", "1")) + self.app.settings.value("Segments", "1") + ) self.input_segments.setAlignment(QtCore.Qt.AlignRight) self.input_segments.setFixedHeight(20) self.input_segments.setFixedWidth(60) @@ -85,7 +86,8 @@ class SweepControl(Control): self.label_step = QtWidgets.QLabel("Hz/step") self.label_step.setAlignment( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter + ) segment_layout = QtWidgets.QHBoxLayout() segment_layout.addWidget(self.input_segments) @@ -95,7 +97,8 @@ class SweepControl(Control): btn_settings_window = QtWidgets.QPushButton("Sweep settings ...") btn_settings_window.setFixedHeight(20) btn_settings_window.clicked.connect( - lambda: self.app.display_window("sweep_settings")) + lambda: self.app.display_window("sweep_settings") + ) self.layout.addRow(btn_settings_window) @@ -206,8 +209,7 @@ class SweepControl(Control): segments = self.get_segments() if segments > 0: fstep = fspan / (segments * self.app.vna.datapoints - 1) - self.label_step.setText( - f"{format_frequency_short(fstep)}/step") + self.label_step.setText(f"{format_frequency_short(fstep)}/step") self.update_sweep() def update_sweep(self): diff --git a/src/NanoVNASaver/Defaults.py b/src/NanoVNASaver/Defaults.py index 68d4901..38bcdd4 100644 --- a/src/NanoVNASaver/Defaults.py +++ b/src/NanoVNASaver/Defaults.py @@ -43,12 +43,12 @@ class GUI: @DC.dataclass class ChartsSelected: - chart_00: str = 'S11 Smith Chart' - chart_01: str = 'S11 Return Loss' - chart_02: str = 'None' - chart_10: str = 'S21 Polar Plot' - chart_11: str = 'S21 Gain' - chart_12: str = 'None' + chart_00: str = "S11 Smith Chart" + chart_01: str = "S11 Return Loss" + chart_02: str = "None" + chart_10: str = "S21 Polar Plot" + chart_11: str = "S21 Gain" + chart_12: str = "None" @DC.dataclass @@ -69,33 +69,49 @@ class Chart: @DC.dataclass class ChartColors: # pylint: disable=too-many-instance-attributes background: QColor = DC.field( - default_factory=lambda: QColor(QtCore.Qt.white)) + default_factory=lambda: QColor(QtCore.Qt.white) + ) foreground: QColor = DC.field( - default_factory=lambda: QColor(QtCore.Qt.lightGray)) + default_factory=lambda: QColor(QtCore.Qt.lightGray) + ) reference: QColor = DC.field(default_factory=lambda: QColor(0, 0, 255, 64)) reference_secondary: QColor = DC.field( - default_factory=lambda: QColor(0, 0, 192, 48)) + default_factory=lambda: QColor(0, 0, 192, 48) + ) sweep: QColor = DC.field( - default_factory=lambda: QColor(QtCore.Qt.darkYellow)) + default_factory=lambda: QColor(QtCore.Qt.darkYellow) + ) sweep_secondary: QColor = DC.field( - default_factory=lambda: QColor(QtCore.Qt.darkMagenta)) - swr: QColor = DC.field( - default_factory=lambda: QColor(255, 0, 0, 128)) - text: QColor = DC.field( - default_factory=lambda: QColor(QtCore.Qt.black)) - bands: QColor = DC.field( - default_factory=lambda: QColor(128, 128, 128, 48)) + default_factory=lambda: QColor(QtCore.Qt.darkMagenta) + ) + swr: QColor = DC.field(default_factory=lambda: QColor(255, 0, 0, 128)) + text: QColor = DC.field(default_factory=lambda: QColor(QtCore.Qt.black)) + bands: QColor = DC.field(default_factory=lambda: QColor(128, 128, 128, 48)) @DC.dataclass class Markers: - active_labels: list = DC.field(default_factory=lambda: [ - "actualfreq", "impedance", "serr", "serl", "serc", "parr", "parlc", - "vswr", "returnloss", "s11q", "s11phase", "s21gain", "s21phase", - ]) + active_labels: list = DC.field( + default_factory=lambda: [ + "actualfreq", + "impedance", + "serr", + "serl", + "serc", + "parr", + "parlc", + "vswr", + "returnloss", + "s11q", + "s11phase", + "s21gain", + "s21phase", + ] + ) colored_names: bool = True color_0: QColor = DC.field( - default_factory=lambda: QColor(QtCore.Qt.darkGray)) + default_factory=lambda: QColor(QtCore.Qt.darkGray) + ) color_1: QColor = DC.field(default_factory=lambda: QColor(255, 0, 0)) color_2: QColor = DC.field(default_factory=lambda: QColor(0, 255, 0)) color_3: QColor = DC.field(default_factory=lambda: QColor(0, 0, 255)) @@ -103,37 +119,34 @@ class Markers: color_5: QColor = DC.field(default_factory=lambda: QColor(255, 0, 255)) color_6: QColor = DC.field(default_factory=lambda: QColor(255, 255, 0)) color_7: QColor = DC.field( - default_factory=lambda: QColor(QtCore.Qt.lightGray)) + default_factory=lambda: QColor(QtCore.Qt.lightGray) + ) @DC.dataclass class CFG: - gui: object = DC.field( - default_factory=lambda: GUI()) - charts_selected: object = DC.field( - default_factory=lambda: ChartsSelected()) - chart: object = DC.field( - default_factory=lambda: Chart()) - chart_colors: object = DC.field( - default_factory=lambda: ChartColors()) - markers: object = DC.field( - default_factory=lambda: Markers()) + gui: object = DC.field(default_factory=lambda: GUI()) + charts_selected: object = DC.field(default_factory=lambda: ChartsSelected()) + chart: object = DC.field(default_factory=lambda: Chart()) + chart_colors: object = DC.field(default_factory=lambda: ChartColors()) + markers: object = DC.field(default_factory=lambda: Markers()) cfg = CFG() -def restore(settings: 'AppSettings') -> CFG: +def restore(settings: "AppSettings") -> CFG: result = CFG() for field in DC.fields(result): - value = settings.restore_dataclass(field.name.upper(), - getattr(result, field.name)) + value = settings.restore_dataclass( + field.name.upper(), getattr(result, field.name) + ) setattr(result, field.name, value) logger.debug("restored\n(\n%s\n)", result) return result -def store(settings: 'AppSettings', data: CFG = None) -> None: +def store(settings: "AppSettings", data: CFG = None) -> None: data = data or cfg logger.debug("storing\n(\n%s\n)", data) assert isinstance(data, CFG) @@ -147,25 +160,25 @@ def from_type(data) -> str: type_map = { bytearray: lambda x: x.hex(), QColor: lambda x: x.getRgb(), - QByteArray: lambda x: x.toHex() + QByteArray: lambda x: x.toHex(), } - return (f"{type_map[type(data)](data)}" if - type(data) in type_map else - f"{data}") + return ( + f"{type_map[type(data)](data)}" if type(data) in type_map else f"{data}" + ) def to_type(data: object, data_type: type) -> object: type_map = { - bool: lambda x: x.lower() == 'true', + bool: lambda x: x.lower() == "true", bytearray: bytearray.fromhex, list: literal_eval, tuple: literal_eval, QColor: lambda x: QColor.fromRgb(*literal_eval(x)), - QByteArray: lambda x: QByteArray.fromHex(literal_eval(x)) + QByteArray: lambda x: QByteArray.fromHex(literal_eval(x)), } - return (type_map[data_type](data) if - data_type in type_map else - data_type(data)) + return ( + type_map[data_type](data) if data_type in type_map else data_type(data) + ) # noinspection PyDataclass @@ -178,8 +191,13 @@ class AppSettings(QSettings): try: assert isinstance(value, field.type) except AssertionError as exc: - logger.error("%s: %s of type %s is not a %s", - name, field.name, type(value), field.type) + logger.error( + "%s: %s of type %s is not a %s", + name, + field.name, + type(value), + field.type, + ) raise TypeError from exc self.setValue(field.name, from_type(value)) self.endGroup() diff --git a/src/NanoVNASaver/Formatting.py b/src/NanoVNASaver/Formatting.py index e429393..6a1093a 100644 --- a/src/NanoVNASaver/Formatting.py +++ b/src/NanoVNASaver/Formatting.py @@ -27,22 +27,27 @@ FMT_FREQ_SHORT = SITools.Format(max_nr_digits=4) FMT_FREQ_SPACE = SITools.Format(space_str=" ") FMT_FREQ_SWEEP = SITools.Format(max_nr_digits=9, allow_strip=True) FMT_FREQ_INPUTS = SITools.Format( - max_nr_digits=10, allow_strip=True, - printable_min=0, unprintable_under="- ") + max_nr_digits=10, allow_strip=True, printable_min=0, unprintable_under="- " +) FMT_Q_FACTOR = SITools.Format( - max_nr_digits=4, assume_infinity=False, - min_offset=0, max_offset=0, allow_strip=True) + max_nr_digits=4, + assume_infinity=False, + min_offset=0, + max_offset=0, + allow_strip=True, +) FMT_GROUP_DELAY = SITools.Format(max_nr_digits=5, space_str=" ") FMT_REACT = SITools.Format(max_nr_digits=5, space_str=" ", allow_strip=True) -FMT_COMPLEX = SITools.Format(max_nr_digits=3, allow_strip=True, - printable_min=0, unprintable_under="- ") +FMT_COMPLEX = SITools.Format( + max_nr_digits=3, allow_strip=True, printable_min=0, unprintable_under="- " +) FMT_COMPLEX_NEG = SITools.Format(max_nr_digits=3, allow_strip=True) FMT_SHORT = SITools.Format(max_nr_digits=4) FMT_WAVELENGTH = SITools.Format(max_nr_digits=4, space_str=" ") -FMT_PARSE = SITools.Format(parse_sloppy_unit=True, parse_sloppy_kilo=True, - parse_clamp_min=0) -FMT_PARSE_VALUE = SITools.Format( - parse_sloppy_unit=True, parse_sloppy_kilo=True) +FMT_PARSE = SITools.Format( + parse_sloppy_unit=True, parse_sloppy_kilo=True, parse_clamp_min=0 +) +FMT_PARSE_VALUE = SITools.Format(parse_sloppy_unit=True, parse_sloppy_kilo=True) FMT_VSWR = SITools.Format(max_nr_digits=3) @@ -117,7 +122,7 @@ def format_group_delay(val: float) -> str: def format_phase(val: float) -> str: - return f"{math.degrees(val):.2f}""\N{DEGREE SIGN}" + return f"{math.degrees(val):.2f}" "\N{DEGREE SIGN}" def format_complex_adm(z: complex, allow_negative: bool = False) -> str: @@ -135,7 +140,7 @@ def format_complex_imp(z: complex, allow_negative: bool = False) -> str: fmt_re = FMT_COMPLEX_NEG if allow_negative else FMT_COMPLEX re = SITools.Value(z.real, fmt=fmt_re) im = SITools.Value(abs(z.imag), fmt=FMT_COMPLEX) - return f"{re}{'-' if z.imag < 0 else '+'}j{im} ""\N{OHM SIGN}" + return f"{re}{'-' if z.imag < 0 else '+'}j{im} " "\N{OHM SIGN}" def format_wavelength(length: Number) -> str: @@ -153,10 +158,11 @@ def parse_frequency(freq: str) -> int: return -1 -def parse_value(val: str, unit: str = "", - fmt: SITools.Format = FMT_PARSE_VALUE) -> float: +def parse_value( + val: str, unit: str = "", fmt: SITools.Format = FMT_PARSE_VALUE +) -> float: try: - val.replace(',', '.') + val.replace(",", ".") return float(SITools.Value(val, unit, fmt)) except (ValueError, IndexError): return 0.0 diff --git a/src/NanoVNASaver/Hardware/Hardware.py b/src/NanoVNASaver/Hardware/Hardware.py index 32f31f1..ed751f9 100644 --- a/src/NanoVNASaver/Hardware/Hardware.py +++ b/src/NanoVNASaver/Hardware/Hardware.py @@ -43,8 +43,8 @@ USBDevice = namedtuple("Device", "vid pid name") USBDEVICETYPES = ( USBDevice(0x0483, 0x5740, "NanoVNA"), - USBDevice(0x16c0, 0x0483, "AVNA"), - USBDevice(0x04b4, 0x0008, "S-A-A-2"), + USBDevice(0x16C0, 0x0483, "AVNA"), + USBDevice(0x04B4, 0x0008, "S-A-A-2"), ) RETRIES = 3 TIMEOUT = 0.2 @@ -71,15 +71,21 @@ NAME2DEVICE = { def _fix_v2_hwinfo(dev): # if dev.hwid == r'PORTS\VID_04B4&PID_0008\DEMO': - if r'PORTS\VID_04B4&PID_0008' in dev.hwid: - dev.vid, dev.pid = 0x04b4, 0x0008 + if r"PORTS\VID_04B4&PID_0008" in dev.hwid: + dev.vid, dev.pid = 0x04B4, 0x0008 return dev def usb_typename(device: ListPortInfo) -> str: - return next((t.name for t in USBDEVICETYPES if - device.vid == t.vid and device.pid == t.pid), - "") + return next( + ( + t.name + for t in USBDEVICETYPES + if device.vid == t.vid and device.pid == t.pid + ), + "", + ) + # Get list of interfaces with VNAs connected @@ -88,13 +94,18 @@ def get_interfaces() -> List[Interface]: interfaces = [] # serial like usb interfaces for d in list_ports.comports(): - if platform.system() == 'Windows' and d.vid is None: + if platform.system() == "Windows" and d.vid is None: d = _fix_v2_hwinfo(d) if not (typename := usb_typename(d)): continue - logger.debug("Found %s USB:(%04x:%04x) on port %s", - typename, d.vid, d.pid, d.device) - iface = Interface('serial', typename) + logger.debug( + "Found %s USB:(%04x:%04x) on port %s", + typename, + d.vid, + d.pid, + d.device, + ) + iface = Interface("serial", typename) iface.port = d.device iface.open() iface.comment = get_comment(iface) @@ -109,9 +120,8 @@ def get_portinfos() -> List[str]: portinfos = [] # serial like usb interfaces for d in list_ports.comports(): - logger.debug("Found USB:(%04x:%04x) on port %s", - d.vid, d.pid, d.device) - iface = Interface('serial', "DEBUG") + logger.debug("Found USB:(%04x:%04x) on port %s", d.vid, d.pid, d.device) + iface = Interface("serial", "DEBUG") iface.port = d.device iface.open() version = detect_version(iface) @@ -130,19 +140,19 @@ def get_comment(iface: Interface) -> str: with iface.lock: vna_version = detect_version(iface) - if vna_version == 'v2': + if vna_version == "v2": return "S-A-A-2" logger.info("Finding firmware variant...") info = get_info(iface) for search, name in ( - ("AVNA + Teensy", "AVNA"), - ("NanoVNA-H 4", "H4"), - ("NanoVNA-H", "H"), - ("NanoVNA-F_V2", "F_V2"), - ("NanoVNA-F", "F"), - ("NanoVNA", "NanoVNA"), - ("tinySA", "tinySA"), + ("AVNA + Teensy", "AVNA"), + ("NanoVNA-H 4", "H4"), + ("NanoVNA-H", "H"), + ("NanoVNA-F_V2", "F_V2"), + ("NanoVNA-F", "F"), + ("NanoVNA", "NanoVNA"), + ("tinySA", "tinySA"), ): if info.find(search) >= 0: return name @@ -171,7 +181,7 @@ def detect_version(serial_port: serial.Serial) -> str: if data.startswith("2"): return "v2" logger.debug("Retry detection: %s", i + 1) - logger.error('No VNA detected. Hardware responded to CR with: %s', data) + logger.error("No VNA detected. Hardware responded to CR with: %s", data) return "" diff --git a/src/NanoVNASaver/Hardware/NanoVNA.py b/src/NanoVNASaver/Hardware/NanoVNA.py index e572045..15eae04 100644 --- a/src/NanoVNASaver/Hardware/NanoVNA.py +++ b/src/NanoVNASaver/Hardware/NanoVNA.py @@ -46,7 +46,6 @@ class NanoVNA(VNA): self._sweepdata = [] def _get_running_frequencies(self): - logger.debug("Reading values: frequencies") try: frequencies = super().readValues("frequencies") @@ -61,24 +60,27 @@ class NanoVNA(VNA): timeout = self.serial.timeout with self.serial.lock: drain_serial(self.serial) - self.serial.write("capture\r".encode('ascii')) + self.serial.write("capture\r".encode("ascii")) self.serial.readline() self.serial.timeout = 4 image_data = self.serial.read( - self.screenwidth * self.screenheight * 2) + self.screenwidth * self.screenheight * 2 + ) self.serial.timeout = timeout self.serial.timeout = timeout return image_data def _convert_data(self, image_data: bytes) -> bytes: rgb_data = struct.unpack( - f">{self.screenwidth * self.screenheight}H", - image_data) + f">{self.screenwidth * self.screenheight}H", image_data + ) rgb_array = np.array(rgb_data, dtype=np.uint32) - return (0xFF000000 + - ((rgb_array & 0xF800) << 8) + - ((rgb_array & 0x07E0) << 5) + - ((rgb_array & 0x001F) << 3)) + return ( + 0xFF000000 + + ((rgb_array & 0xF800) << 8) + + ((rgb_array & 0x07E0) << 5) + + ((rgb_array & 0x001F) << 3) + ) def getScreenshot(self) -> QtGui.QPixmap: logger.debug("Capturing screenshot...") @@ -90,12 +92,12 @@ class NanoVNA(VNA): rgba_array, self.screenwidth, self.screenheight, - QtGui.QImage.Format_ARGB32) + QtGui.QImage.Format_ARGB32, + ) logger.debug("Captured screenshot") return QtGui.QPixmap(image) except serial.SerialException as exc: - logger.exception( - "Exception while capturing screenshot: %s", exc) + logger.exception("Exception while capturing screenshot: %s", exc) return QtGui.QPixmap() def resetSweep(self, start: int, stop: int): @@ -125,8 +127,12 @@ class NanoVNA(VNA): logger.debug("readFrequencies: %s", self.sweep_method) if self.sweep_method != "scan_mask": return super().readFrequencies() - return [int(line) for line in self.exec_command( - f"scan {self.start} {self.stop} {self.datapoints} 0b001")] + return [ + int(line) + for line in self.exec_command( + f"scan {self.start} {self.stop} {self.datapoints} 0b001" + ) + ] def readValues(self, value) -> List[str]: if self.sweep_method != "scan_mask": @@ -137,11 +143,12 @@ class NanoVNA(VNA): if value == "data 0": self._sweepdata = [] for line in self.exec_command( - f"scan {self.start} {self.stop} {self.datapoints} 0b110"): + f"scan {self.start} {self.stop} {self.datapoints} 0b110" + ): data = line.split() - self._sweepdata.append(( - f"{data[0]} {data[1]}", - f"{data[2]} {data[3]}")) + self._sweepdata.append( + (f"{data[0]} {data[1]}", f"{data[2]} {data[3]}") + ) if value == "data 0": return [x[0] for x in self._sweepdata] if value == "data 1": diff --git a/src/NanoVNASaver/Hardware/NanoVNA_F_V2.py b/src/NanoVNASaver/Hardware/NanoVNA_F_V2.py index b23d44f..4d4b9fa 100644 --- a/src/NanoVNASaver/Hardware/NanoVNA_F_V2.py +++ b/src/NanoVNASaver/Hardware/NanoVNA_F_V2.py @@ -46,10 +46,10 @@ class NanoVNA_F_V2(NanoVNA): rgba_array, self.screenwidth, self.screenheight, - QtGui.QImage.Format_RGB16) + QtGui.QImage.Format_RGB16, + ) logger.debug("Captured screenshot") return QtGui.QPixmap(image) except serial.SerialException as exc: - logger.exception( - "Exception while capturing screenshot: %s", exc) + logger.exception("Exception while capturing screenshot: %s", exc) return QtGui.QPixmap() diff --git a/src/NanoVNASaver/Hardware/NanoVNA_V2.py b/src/NanoVNASaver/Hardware/NanoVNA_V2.py index f60e1e0..d49364b 100644 --- a/src/NanoVNASaver/Hardware/NanoVNA_V2.py +++ b/src/NanoVNASaver/Hardware/NanoVNA_V2.py @@ -26,13 +26,13 @@ from NanoVNASaver.Hardware.Serial import Interface from NanoVNASaver.Hardware.VNA import VNA from NanoVNASaver.Version import Version -if platform.system() != 'Windows': +if platform.system() != "Windows": import tty logger = logging.getLogger(__name__) _CMD_NOP = 0x00 -_CMD_INDICATE = 0x0d +_CMD_INDICATE = 0x0D _CMD_READ = 0x10 _CMD_READ2 = 0x11 _CMD_READ4 = 0x12 @@ -49,22 +49,23 @@ _ADDR_SWEEP_POINTS = 0x20 _ADDR_SWEEP_VALS_PER_FREQ = 0x22 _ADDR_RAW_SAMPLES_MODE = 0x26 _ADDR_VALUES_FIFO = 0x30 -_ADDR_DEVICE_VARIANT = 0xf0 -_ADDR_PROTOCOL_VERSION = 0xf1 -_ADDR_HARDWARE_REVISION = 0xf2 -_ADDR_FW_MAJOR = 0xf3 -_ADDR_FW_MINOR = 0xf4 +_ADDR_DEVICE_VARIANT = 0xF0 +_ADDR_PROTOCOL_VERSION = 0xF1 +_ADDR_HARDWARE_REVISION = 0xF2 +_ADDR_FW_MAJOR = 0xF3 +_ADDR_FW_MINOR = 0xF4 WRITE_SLEEP = 0.05 _ADF4350_TXPOWER_DESC_MAP = { - 0: '9dB attenuation', - 1: '6dB attenuation', - 2: '3dB attenuation', - 3: 'Maximum', + 0: "9dB attenuation", + 1: "6dB attenuation", + 2: "3dB attenuation", + 3: "Maximum", } _ADF4350_TXPOWER_DESC_REV_MAP = { - value: key for key, value in _ADF4350_TXPOWER_DESC_MAP.items()} + value: key for key, value in _ADF4350_TXPOWER_DESC_MAP.items() +} class NanoVNA_V2(VNA): @@ -76,7 +77,7 @@ class NanoVNA_V2(VNA): def __init__(self, iface: Interface): super().__init__(iface) - if platform.system() != 'Windows': + if platform.system() != "Windows": tty.setraw(self.serial.fd) # reset protocol to known state @@ -85,8 +86,8 @@ class NanoVNA_V2(VNA): sleep(WRITE_SLEEP) # firmware major version of 0xff indicates dfu mode - if self.version.data["major"] == 0xff: - raise IOError('Device is in DFU mode') + if self.version.data["major"] == 0xFF: + raise IOError("Device is in DFU mode") if "S21 hack" in self.features: self.valid_datapoints = (101, 11, 51, 201, 301, 501, 1021) @@ -116,8 +117,13 @@ class NanoVNA_V2(VNA): self.features.update({"Set TX power partial", "Set Average"}) # Can only set ADF4350 power, i.e. for >= 140MHz self.txPowerRanges = [ - ((140e6, self.sweep_max_freq_Hz), - [_ADF4350_TXPOWER_DESC_MAP[value] for value in (3, 2, 1, 0)]), + ( + (140e6, self.sweep_max_freq_Hz), + [ + _ADF4350_TXPOWER_DESC_MAP[value] + for value in (3, 2, 1, 0) + ], + ), ] def readFirmware(self) -> str: @@ -135,9 +141,15 @@ class NanoVNA_V2(VNA): freq_index = -1 for i in range(pointstoread): - (fwd_real, fwd_imag, rev0_real, rev0_imag, rev1_real, - rev1_imag, freq_index) = unpack_from( - " 'Version': - result = self._read_version(_ADDR_FW_MAJOR, - _ADDR_FW_MINOR) + def readVersion(self) -> "Version": + result = self._read_version(_ADDR_FW_MAJOR, _ADDR_FW_MINOR) logger.debug("readVersion: %s", result) return result - def read_board_revision(self) -> 'Version': - result = self._read_version(_ADDR_DEVICE_VARIANT, - _ADDR_HARDWARE_REVISION) + def read_board_revision(self) -> "Version": + result = self._read_version( + _ADDR_DEVICE_VARIANT, _ADDR_HARDWARE_REVISION + ) logger.debug("read_board_revision: %s", result) return result @@ -243,34 +261,41 @@ class NanoVNA_V2(VNA): return self.sweepStartHz = start self.sweepStepHz = step - logger.info('NanoVNAV2: set sweep start %d step %d', - self.sweepStartHz, self.sweepStepHz) + logger.info( + "NanoVNAV2: set sweep start %d step %d", + self.sweepStartHz, + self.sweepStepHz, + ) self._updateSweep() return def _updateSweep(self): s21hack = "S21 hack" in self.features - cmd = pack(" ADF4350 self._set_register(0x42, _ADF4350_TXPOWER_DESC_REV_MAP[power_desc], 1) def _set_register(self, addr, value, size): - packet = b'' + packet = b"" if size == 1: packet = pack(" bytes: rgb_data = struct.unpack( - f">{self.screenwidth * self.screenheight}H", - image_data) + f">{self.screenwidth * self.screenheight}H", image_data + ) rgb_array = np.array(rgb_data, dtype=np.uint32) - return (0xFF000000 + - ((rgb_array & 0xF800) << 8) + - ((rgb_array & 0x07E0) << 5) + - ((rgb_array & 0x001F) << 3)) + return ( + 0xFF000000 + + ((rgb_array & 0xF800) << 8) + + ((rgb_array & 0x07E0) << 5) + + ((rgb_array & 0x001F) << 3) + ) def getScreenshot(self) -> QtGui.QPixmap: logger.debug("Capturing screenshot...") @@ -89,12 +91,12 @@ class TinySA(VNA): rgba_array, self.screenwidth, self.screenheight, - QtGui.QImage.Format_ARGB32) + QtGui.QImage.Format_ARGB32, + ) logger.debug("Captured screenshot") return QtGui.QPixmap(image) except serial.SerialException as exc: - logger.exception( - "Exception while capturing screenshot: %s", exc) + logger.exception("Exception while capturing screenshot: %s", exc) return QtGui.QPixmap() def resetSweep(self, start: int, stop: int): @@ -113,6 +115,7 @@ class TinySA(VNA): def readValues(self, value) -> List[str]: logger.debug("Read: %s", value) if value == "data 0": - self._sweepdata = [f"0 {line.strip()}" - for line in self.exec_command("data")] + self._sweepdata = [ + f"0 {line.strip()}" for line in self.exec_command("data") + ] return self._sweepdata diff --git a/src/NanoVNASaver/Hardware/VNA.py b/src/NanoVNASaver/Hardware/VNA.py index edf04fa..6434394 100644 --- a/src/NanoVNASaver/Hardware/VNA.py +++ b/src/NanoVNASaver/Hardware/VNA.py @@ -44,8 +44,11 @@ WAIT = 0.05 def _max_retries(bandwidth: int, datapoints: int) -> int: - return round(20 + 20 * (datapoints / 101) + - (1000 / bandwidth) ** 1.30 * (datapoints / 101)) + return round( + 20 + + 20 * (datapoints / 101) + + (1000 / bandwidth) ** 1.30 * (datapoints / 101) + ) class VNA: @@ -94,7 +97,7 @@ class VNA: logger.debug("exec_command(%s)", command) with self.serial.lock: drain_serial(self.serial) - self.serial.write(f"{command}\r".encode('ascii')) + self.serial.write(f"{command}\r".encode("ascii")) sleep(wait) retries = 0 max_retries = _max_retries(self.bandwidth, self.datapoints) @@ -137,11 +140,14 @@ class VNA: result = result.split(" {")[1].strip("}") return sorted([int(i) for i in result.split("|")]) except IndexError: - return [1000, ] + return [ + 1000, + ] def set_bandwidth(self, bandwidth: int): - bw_val = DISLORD_BW[bandwidth] \ - if self.bw_method == "dislord" else bandwidth + bw_val = ( + DISLORD_BW[bandwidth] if self.bw_method == "dislord" else bandwidth + ) result = " ".join(self.exec_command(f"bandwidth {bw_val}")) if self.bw_method == "ttrftech" and result: raise IOError(f"set_bandwith({bandwidth}: {result}") @@ -191,11 +197,10 @@ class VNA: def readValues(self, value) -> List[str]: logger.debug("VNA reading %s", value) result = list(self.exec_command(value)) - logger.debug("VNA done reading %s (%d values)", - value, len(result)) + logger.debug("VNA done reading %s (%d values)", value, len(result)) return result - def readVersion(self) -> 'Version': + def readVersion(self) -> "Version": result = list(self.exec_command("version")) logger.debug("result:\n%s", result) return Version(result[0]) diff --git a/src/NanoVNASaver/Marker/Delta.py b/src/NanoVNASaver/Marker/Delta.py index ba76093..7c2de90 100644 --- a/src/NanoVNASaver/Marker/Delta.py +++ b/src/NanoVNASaver/Marker/Delta.py @@ -61,71 +61,91 @@ class DeltaMarker(Marker): imp = imp_b - imp_a cap_str = format_capacitance( - RFTools.impedance_to_capacitance(imp_b, s11_b.freq) - - RFTools.impedance_to_capacitance(imp_a, s11_a.freq)) + RFTools.impedance_to_capacitance(imp_b, s11_b.freq) + - RFTools.impedance_to_capacitance(imp_a, s11_a.freq) + ) ind_str = format_inductance( - RFTools.impedance_to_inductance(imp_b, s11_b.freq) - - RFTools.impedance_to_inductance(imp_a, s11_a.freq)) + RFTools.impedance_to_inductance(imp_b, s11_b.freq) + - RFTools.impedance_to_inductance(imp_a, s11_a.freq) + ) imp_p_a = RFTools.serial_to_parallel(imp_a) imp_p_b = RFTools.serial_to_parallel(imp_b) imp_p = imp_p_b - imp_p_a cap_p_str = format_capacitance( - RFTools.impedance_to_capacitance(imp_p_b, s11_b.freq) - - RFTools.impedance_to_capacitance(imp_p_a, s11_a.freq)) + RFTools.impedance_to_capacitance(imp_p_b, s11_b.freq) + - RFTools.impedance_to_capacitance(imp_p_a, s11_a.freq) + ) ind_p_str = format_inductance( - RFTools.impedance_to_inductance(imp_p_b, s11_b.freq) - - RFTools.impedance_to_inductance(imp_p_a, s11_a.freq)) + RFTools.impedance_to_inductance(imp_p_b, s11_b.freq) + - RFTools.impedance_to_inductance(imp_p_a, s11_a.freq) + ) x_str = cap_str if imp.imag < 0 else ind_str x_p_str = cap_p_str if imp_p.imag < 0 else ind_p_str - self.label['actualfreq'].setText( - format_frequency_space(s11_b.freq - s11_a.freq)) - self.label['lambda'].setText( - format_wavelength(s11_b.wavelength - s11_a.wavelength)) - self.label['admittance'].setText(format_complex_adm(imp_p, True)) - self.label['impedance'].setText(format_complex_imp(imp, True)) + self.label["actualfreq"].setText( + format_frequency_space(s11_b.freq - s11_a.freq) + ) + self.label["lambda"].setText( + format_wavelength(s11_b.wavelength - s11_a.wavelength) + ) + self.label["admittance"].setText(format_complex_adm(imp_p, True)) + self.label["impedance"].setText(format_complex_imp(imp, True)) - self.label['parc'].setText(cap_p_str) - self.label['parl'].setText(ind_p_str) - self.label['parlc'].setText(x_p_str) + self.label["parc"].setText(cap_p_str) + self.label["parl"].setText(ind_p_str) + self.label["parlc"].setText(x_p_str) - self.label['parr'].setText(format_resistance(imp_p.real, True)) - self.label['returnloss'].setText( - format_gain(s11_b.gain - s11_a.gain, self.returnloss_is_positive)) - self.label['s11groupdelay'].setText(format_group_delay( - RFTools.groupDelay(b.s11, 1) - - RFTools.groupDelay(a.s11, 1))) + self.label["parr"].setText(format_resistance(imp_p.real, True)) + self.label["returnloss"].setText( + format_gain(s11_b.gain - s11_a.gain, self.returnloss_is_positive) + ) + self.label["s11groupdelay"].setText( + format_group_delay( + RFTools.groupDelay(b.s11, 1) - RFTools.groupDelay(a.s11, 1) + ) + ) - self.label['s11mag'].setText( - format_magnitude(abs(s11_b.z) - abs(s11_a.z))) - self.label['s11phase'].setText(format_phase(s11_b.phase - s11_a.phase)) - self.label['s11polar'].setText( + self.label["s11mag"].setText( + format_magnitude(abs(s11_b.z) - abs(s11_a.z)) + ) + self.label["s11phase"].setText(format_phase(s11_b.phase - s11_a.phase)) + self.label["s11polar"].setText( f"{round(abs(s11_b.z) - abs(s11_a.z), 2)}∠" - f"{format_phase(s11_b.phase - s11_a.phase)}") - self.label['s11q'].setText(format_q_factor( - s11_b.qFactor() - s11_a.qFactor(), True)) - self.label['s11z'].setText(format_resistance(abs(imp))) - self.label['serc'].setText(cap_str) - self.label['serl'].setText(ind_str) - self.label['serlc'].setText(x_str) - self.label['serr'].setText(format_resistance(imp.real, True)) - self.label['vswr'].setText(format_vswr(s11_b.vswr - s11_a.vswr)) + f"{format_phase(s11_b.phase - s11_a.phase)}" + ) + self.label["s11q"].setText( + format_q_factor(s11_b.qFactor() - s11_a.qFactor(), True) + ) + self.label["s11z"].setText(format_resistance(abs(imp))) + self.label["serc"].setText(cap_str) + self.label["serl"].setText(ind_str) + self.label["serlc"].setText(x_str) + self.label["serr"].setText(format_resistance(imp.real, True)) + self.label["vswr"].setText(format_vswr(s11_b.vswr - s11_a.vswr)) if len(a.s21) == len(a.s11): s21_a = a.s21[1] s21_b = b.s21[1] - self.label['s21gain'].setText(format_gain( - s21_b.gain - s21_a.gain)) - self.label['s21groupdelay'].setText(format_group_delay( - (RFTools.groupDelay(b.s21, 1) - - RFTools.groupDelay(a.s21, 1)) / 2)) - self.label['s21mag'].setText(format_magnitude( - abs(s21_b.z) - abs(s21_a.z))) - self.label['s21phase'].setText(format_phase( - s21_b.phase - s21_a.phase)) - self.label['s21polar'].setText( + self.label["s21gain"].setText(format_gain(s21_b.gain - s21_a.gain)) + self.label["s21groupdelay"].setText( + format_group_delay( + ( + RFTools.groupDelay(b.s21, 1) + - RFTools.groupDelay(a.s21, 1) + ) + / 2 + ) + ) + self.label["s21mag"].setText( + format_magnitude(abs(s21_b.z) - abs(s21_a.z)) + ) + self.label["s21phase"].setText( + format_phase(s21_b.phase - s21_a.phase) + ) + self.label["s21polar"].setText( f"{round(abs(s21_b.z) - abs(s21_a.z), 2)}∠" - f"{format_phase(s21_b.phase - s21_a.phase)}") + f"{format_phase(s21_b.phase - s21_a.phase)}" + ) diff --git a/src/NanoVNASaver/Marker/Values.py b/src/NanoVNASaver/Marker/Values.py index 44d4a6e..97b0a81 100644 --- a/src/NanoVNASaver/Marker/Values.py +++ b/src/NanoVNASaver/Marker/Values.py @@ -56,10 +56,10 @@ TYPES = ( Label("s21groupdelay", "S21 Group Delay", "S21 Group Delay", False), Label("s21magshunt", "S21 |Z| shunt", "S21 Z Magnitude shunt", False), Label("s21magseries", "S21 |Z| series", "S21 Z Magnitude series", False), - Label("s21realimagshunt", "S21 R+jX shunt", - "S21 Z Real+Imag shunt", False), - Label("s21realimagseries", "S21 R+jX series", - "S21 Z Real+Imag series", False), + Label("s21realimagshunt", "S21 R+jX shunt", "S21 Z Real+Imag shunt", False), + Label( + "s21realimagseries", "S21 R+jX series", "S21 Z Real+Imag series", False + ), ) @@ -67,31 +67,40 @@ def default_label_ids() -> str: return [label.label_id for label in TYPES if label.default_active] -class Value(): +class Value: """Contains the data area to calculate marker values from""" - def __init__(self, freq: int = 0, - s11: List[Datapoint] = None, - s21: List[Datapoint] = None): + def __init__( + self, + freq: int = 0, + s11: List[Datapoint] = None, + s21: List[Datapoint] = None, + ): self.freq = freq self.s11 = [] if s11 is None else s11[:] self.s21 = [] if s21 is None else s21[:] - def store(self, index: int, - s11: List[Datapoint], - s21: List[Datapoint]): + def store(self, index: int, s11: List[Datapoint], s21: List[Datapoint]): # handle boundaries if index == 0: index = 1 - s11 = [s11[0], ] + s11 + s11 = [ + s11[0], + ] + s11 if s21: - s21 = [s21[0], ] + s21 + s21 = [ + s21[0], + ] + s21 if index == len(s11): - s11 += [s11[-1], ] + s11 += [ + s11[-1], + ] if s21: - s21 += [s21[-1], ] + s21 += [ + s21[-1], + ] self.freq = s11[1].freq - self.s11 = s11[index - 1:index + 2] + self.s11 = s11[index - 1 : index + 2] if s21: - self.s21 = s21[index - 1:index + 2] + self.s21 = s21[index - 1 : index + 2] diff --git a/src/NanoVNASaver/Marker/Widget.py b/src/NanoVNASaver/Marker/Widget.py index 4411609..634c816 100644 --- a/src/NanoVNASaver/Marker/Widget.py +++ b/src/NanoVNASaver/Marker/Widget.py @@ -81,7 +81,8 @@ class Marker(QtCore.QObject, Value): if self.qsettings: Marker._instances += 1 Marker.active_labels = self.qsettings.value( - "MarkerFields", defaultValue=default_label_ids()) + "MarkerFields", defaultValue=default_label_ids() + ) self.index = Marker._instances if not self.name: @@ -92,7 +93,9 @@ class Marker(QtCore.QObject, Value): self.frequencyInput.setAlignment(QtCore.Qt.AlignRight) self.frequencyInput.editingFinished.connect( lambda: self.setFrequency( - parse_frequency(self.frequencyInput.text()))) + parse_frequency(self.frequencyInput.text()) + ) + ) ############################################################### # Data display labels @@ -101,8 +104,8 @@ class Marker(QtCore.QObject, Value): self.label = { label.label_id: MarkerLabel(label.name) for label in TYPES } - self.label['actualfreq'].setMinimumWidth(100) - self.label['returnloss'].setMinimumWidth(80) + self.label["actualfreq"].setMinimumWidth(100) + self.label["returnloss"].setMinimumWidth(80) ############################################################### # Marker control layout @@ -112,8 +115,11 @@ class Marker(QtCore.QObject, Value): self.btnColorPicker.setMinimumHeight(20) self.btnColorPicker.setFixedWidth(20) self.btnColorPicker.clicked.connect( - lambda: self.setColor(QtWidgets.QColorDialog.getColor( - self.color, options=QtWidgets.QColorDialog.ShowAlphaChannel)) + lambda: self.setColor( + QtWidgets.QColorDialog.getColor( + self.color, options=QtWidgets.QColorDialog.ShowAlphaChannel + ) + ) ) self.isMouseControlledRadioButton = QtWidgets.QRadioButton() @@ -133,7 +139,9 @@ class Marker(QtCore.QObject, Value): try: self.setColor( self.qsettings.value( - f"Marker{self.count()}Color", COLORS[self.count()])) + f"Marker{self.count()}Color", COLORS[self.count()] + ) + ) except AttributeError: # happens when qsettings == None self.setColor(COLORS[1]) except IndexError: @@ -159,8 +167,7 @@ class Marker(QtCore.QObject, Value): def _add_active_labels(self, label_id, form): if label_id in self.label: - form.addRow( - f"{self.label[label_id].name}:", self.label[label_id]) + form.addRow(f"{self.label[label_id].name}:", self.label[label_id]) self.label[label_id].show() def _size_str(self) -> str: @@ -171,9 +178,9 @@ class Marker(QtCore.QObject, Value): def setScale(self, scale): self.group_box.setMaximumWidth(int(340 * scale)) - self.label['actualfreq'].setMinimumWidth(int(100 * scale)) - self.label['actualfreq'].setMinimumWidth(int(100 * scale)) - self.label['returnloss'].setMinimumWidth(int(80 * scale)) + self.label["actualfreq"].setMinimumWidth(int(100 * scale)) + self.label["actualfreq"].setMinimumWidth(int(100 * scale)) + self.label["returnloss"].setMinimumWidth(int(80 * scale)) if self.coloredText: color_string = QtCore.QVariant(self.color) color_string.convert(QtCore.QVariant.String) @@ -259,8 +266,10 @@ class Marker(QtCore.QObject, Value): upper_stepsize = data[-1].freq - data[-2].freq # We are outside the bounds of the data, so we can't put in a marker - if (self.freq + lower_stepsize / 2 < min_freq or - self.freq - upper_stepsize / 2 > max_freq): + if ( + self.freq + lower_stepsize / 2 < min_freq + or self.freq - upper_stepsize / 2 > max_freq + ): return min_distance = max_freq @@ -286,15 +295,16 @@ class Marker(QtCore.QObject, Value): for v in self.label.values(): v.setText("") - def updateLabels(self, - s11: List[RFTools.Datapoint], - s21: List[RFTools.Datapoint]): + def updateLabels( + self, s11: List[RFTools.Datapoint], s21: List[RFTools.Datapoint] + ): if not s11: return if self.location == -1: # initial position try: location = (self.index - 1) / ( - (self._instances - 1) * (len(s11) - 1)) + (self._instances - 1) * (len(s11) - 1) + ) self.location = int(location) except ZeroDivisionError: self.location = 0 @@ -309,63 +319,72 @@ class Marker(QtCore.QObject, Value): imp = _s11.impedance() cap_str = format_capacitance( - RFTools.impedance_to_capacitance(imp, _s11.freq)) + RFTools.impedance_to_capacitance(imp, _s11.freq) + ) ind_str = format_inductance( - RFTools.impedance_to_inductance(imp, _s11.freq)) + RFTools.impedance_to_inductance(imp, _s11.freq) + ) imp_p = RFTools.serial_to_parallel(imp) cap_p_str = format_capacitance( - RFTools.impedance_to_capacitance(imp_p, _s11.freq)) + RFTools.impedance_to_capacitance(imp_p, _s11.freq) + ) ind_p_str = format_inductance( - RFTools.impedance_to_inductance(imp_p, _s11.freq)) + RFTools.impedance_to_inductance(imp_p, _s11.freq) + ) x_str = cap_str if imp.imag < 0 else ind_str x_p_str = cap_p_str if imp_p.imag < 0 else ind_p_str - self.label['actualfreq'].setText(format_frequency_space(_s11.freq)) - self.label['lambda'].setText(format_wavelength(_s11.wavelength)) - self.label['admittance'].setText(format_complex_adm(imp)) - self.label['impedance'].setText(format_complex_imp(imp)) - self.label['parc'].setText(cap_p_str) - self.label['parl'].setText(ind_p_str) - self.label['parlc'].setText(x_p_str) - self.label['parr'].setText(format_resistance(imp_p.real)) - self.label['returnloss'].setText( - format_gain(_s11.gain, self.returnloss_is_positive)) - self.label['s11groupdelay'].setText( - format_group_delay(RFTools.groupDelay(s11, self.location))) - self.label['s11mag'].setText(format_magnitude(abs(_s11.z))) - self.label['s11phase'].setText(format_phase(_s11.phase)) - self.label['s11polar'].setText( - f'{str(round(abs(_s11.z), 2))}∠{format_phase(_s11.phase)}' + self.label["actualfreq"].setText(format_frequency_space(_s11.freq)) + self.label["lambda"].setText(format_wavelength(_s11.wavelength)) + self.label["admittance"].setText(format_complex_adm(imp)) + self.label["impedance"].setText(format_complex_imp(imp)) + self.label["parc"].setText(cap_p_str) + self.label["parl"].setText(ind_p_str) + self.label["parlc"].setText(x_p_str) + self.label["parr"].setText(format_resistance(imp_p.real)) + self.label["returnloss"].setText( + format_gain(_s11.gain, self.returnloss_is_positive) + ) + self.label["s11groupdelay"].setText( + format_group_delay(RFTools.groupDelay(s11, self.location)) + ) + self.label["s11mag"].setText(format_magnitude(abs(_s11.z))) + self.label["s11phase"].setText(format_phase(_s11.phase)) + self.label["s11polar"].setText( + f"{str(round(abs(_s11.z), 2))}∠{format_phase(_s11.phase)}" ) - self.label['s11q'].setText(format_q_factor(_s11.qFactor())) - self.label['s11z'].setText(format_resistance(abs(imp))) - self.label['serc'].setText(cap_str) - self.label['serl'].setText(ind_str) - self.label['serlc'].setText(x_str) - self.label['serr'].setText(format_resistance(imp.real)) - self.label['vswr'].setText(format_vswr(_s11.vswr)) + self.label["s11q"].setText(format_q_factor(_s11.qFactor())) + self.label["s11z"].setText(format_resistance(abs(imp))) + self.label["serc"].setText(cap_str) + self.label["serl"].setText(ind_str) + self.label["serlc"].setText(x_str) + self.label["serr"].setText(format_resistance(imp.real)) + self.label["vswr"].setText(format_vswr(_s11.vswr)) if len(s21) == len(s11): _s21 = s21[self.location] - self.label['s21gain'].setText(format_gain(_s21.gain)) - self.label['s21groupdelay'].setText( - format_group_delay(RFTools.groupDelay(s21, self.location) / 2)) - self.label['s21mag'].setText(format_magnitude(abs(_s21.z))) - self.label['s21phase'].setText(format_phase(_s21.phase)) - self.label['s21polar'].setText( - f'{str(round(abs(_s21.z), 2))}∠{format_phase(_s21.phase)}' + self.label["s21gain"].setText(format_gain(_s21.gain)) + self.label["s21groupdelay"].setText( + format_group_delay(RFTools.groupDelay(s21, self.location) / 2) + ) + self.label["s21mag"].setText(format_magnitude(abs(_s21.z))) + self.label["s21phase"].setText(format_phase(_s21.phase)) + self.label["s21polar"].setText( + f"{str(round(abs(_s21.z), 2))}∠{format_phase(_s21.phase)}" ) - self.label['s21magshunt'].setText( - format_magnitude(abs(_s21.shuntImpedance()))) - self.label['s21magseries'].setText( - format_magnitude(abs(_s21.seriesImpedance()))) - self.label['s21realimagshunt'].setText( - format_complex_imp( - _s21.shuntImpedance(), allow_negative=True)) - self.label['s21realimagseries'].setText( - format_complex_imp( - _s21.seriesImpedance(), allow_negative=True)) + self.label["s21magshunt"].setText( + format_magnitude(abs(_s21.shuntImpedance())) + ) + self.label["s21magseries"].setText( + format_magnitude(abs(_s21.seriesImpedance())) + ) + self.label["s21realimagshunt"].setText( + format_complex_imp(_s21.shuntImpedance(), allow_negative=True) + ) + self.label["s21realimagseries"].setText( + format_complex_imp(_s21.seriesImpedance(), allow_negative=True) + ) diff --git a/src/NanoVNASaver/NanoVNASaver.py b/src/NanoVNASaver/NanoVNASaver.py index fb41e31..21aa3f2 100644 --- a/src/NanoVNASaver/NanoVNASaver.py +++ b/src/NanoVNASaver/NanoVNASaver.py @@ -26,9 +26,14 @@ from PyQt5 import QtWidgets, QtCore, QtGui from NanoVNASaver import Defaults from .Windows import ( - AboutWindow, AnalysisWindow, CalibrationWindow, - DeviceSettingsWindow, DisplaySettingsWindow, SweepSettingsWindow, - TDRWindow, FilesWindow + AboutWindow, + AnalysisWindow, + CalibrationWindow, + DeviceSettingsWindow, + DisplaySettingsWindow, + SweepSettingsWindow, + TDRWindow, + FilesWindow, ) from .Controls.MarkerControl import MarkerControl from .Controls.SweepControl import SweepControl @@ -40,14 +45,26 @@ from .RFTools import corr_att_data from .Charts.Chart import Chart from .Charts import ( CapacitanceChart, - CombinedLogMagChart, GroupDelayChart, InductanceChart, - LogMagChart, PhaseChart, - MagnitudeChart, MagnitudeZChart, MagnitudeZShuntChart, + CombinedLogMagChart, + GroupDelayChart, + InductanceChart, + LogMagChart, + PhaseChart, + MagnitudeChart, + MagnitudeZChart, + MagnitudeZShuntChart, MagnitudeZSeriesChart, - QualityFactorChart, VSWRChart, PermeabilityChart, PolarChart, + QualityFactorChart, + VSWRChart, + PermeabilityChart, + PolarChart, RealImaginaryMuChart, - RealImaginaryZChart, RealImaginaryZShuntChart, RealImaginaryZSeriesChart, - SmithChart, SParameterChart, TDRChart, + RealImaginaryZChart, + RealImaginaryZShuntChart, + RealImaginaryZSeriesChart, + SmithChart, + SParameterChart, + TDRChart, ) from .Calibration import Calibration from .Marker.Widget import Marker @@ -69,10 +86,11 @@ class NanoVNASaver(QtWidgets.QWidget): def __init__(self): super().__init__() self.s21att = 0.0 - if getattr(sys, 'frozen', False): + if getattr(sys, "frozen", False): logger.debug("Running from pyinstaller bundle") self.icon = QtGui.QIcon( - f"{sys._MEIPASS}/icon_48x48.png") # pylint: disable=no-member + f"{sys._MEIPASS}/icon_48x48.png" + ) # pylint: disable=no-member else: self.icon = QtGui.QIcon("icon_48x48.png") self.setWindowIcon(self.icon) @@ -80,7 +98,8 @@ class NanoVNASaver(QtWidgets.QWidget): QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, "NanoVNASaver", - "NanoVNASaver") + "NanoVNASaver", + ) logger.info("Settings from: %s", self.settings.fileName()) Defaults.cfg = Defaults.restore(self.settings) self.threadpool = QtCore.QThreadPool() @@ -128,13 +147,17 @@ class NanoVNASaver(QtWidgets.QWidget): outer.addWidget(scrollarea) self.setLayout(outer) scrollarea.setWidgetResizable(True) - self.resize(Defaults.cfg.gui.window_width, - Defaults.cfg.gui.window_height) + self.resize( + Defaults.cfg.gui.window_width, Defaults.cfg.gui.window_height + ) scrollarea.setSizePolicy( QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding) - self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding) + QtWidgets.QSizePolicy.MinimumExpanding, + ) + self.setSizePolicy( + QtWidgets.QSizePolicy.MinimumExpanding, + QtWidgets.QSizePolicy.MinimumExpanding, + ) widget = QtWidgets.QWidget() widget.setLayout(layout) scrollarea.setWidget(widget) @@ -149,25 +172,30 @@ class NanoVNASaver(QtWidgets.QWidget): "magnitude_z": MagnitudeZChart("S11 |Z|"), "permeability": PermeabilityChart( "S11 R/\N{GREEK SMALL LETTER OMEGA} &" - " X/\N{GREEK SMALL LETTER OMEGA}"), + " X/\N{GREEK SMALL LETTER OMEGA}" + ), "phase": PhaseChart("S11 Phase"), "q_factor": QualityFactorChart("S11 Quality Factor"), "real_imag": RealImaginaryZChart("S11 R+jX"), - "real_imag_mu": RealImaginaryMuChart("S11 \N{GREEK SMALL LETTER MU}"), + "real_imag_mu": RealImaginaryMuChart( + "S11 \N{GREEK SMALL LETTER MU}" + ), "smith": SmithChart("S11 Smith Chart"), "s_parameter": SParameterChart("S11 Real/Imaginary"), "vswr": VSWRChart("S11 VSWR"), }, "s21": { - "group_delay": GroupDelayChart("S21 Group Delay", - reflective=False), + "group_delay": GroupDelayChart( + "S21 Group Delay", reflective=False + ), "log_mag": LogMagChart("S21 Gain"), "magnitude": MagnitudeChart("|S21|"), "magnitude_z_shunt": MagnitudeZShuntChart("S21 |Z| shunt"), "magnitude_z_series": MagnitudeZSeriesChart("S21 |Z| series"), "real_imag_shunt": RealImaginaryZShuntChart("S21 R+jX shunt"), "real_imag_series": RealImaginaryZSeriesChart( - "S21 R+jX series"), + "S21 R+jX series" + ), "phase": PhaseChart("S21 Phase"), "polar": PolarChart("S21 Polar Plot"), "s_parameter": SParameterChart("S21 Real/Imaginary"), @@ -190,8 +218,13 @@ class NanoVNASaver(QtWidgets.QWidget): # List of all charts that can be selected for display self.selectable_charts = ( - self.s11charts + self.s21charts + - self.combinedCharts + [self.tdr_mainwindow_chart, ]) + self.s11charts + + self.s21charts + + self.combinedCharts + + [ + self.tdr_mainwindow_chart, + ] + ) # List of all charts that subscribe to updates (including duplicates!) self.subscribing_charts = [] @@ -314,7 +347,8 @@ class NanoVNASaver(QtWidgets.QWidget): btn_show_analysis = QtWidgets.QPushButton("Analysis ...") btn_show_analysis.setMinimumHeight(20) btn_show_analysis.clicked.connect( - lambda: self.display_window("analysis")) + lambda: self.display_window("analysis") + ) self.marker_column.addWidget(btn_show_analysis) ############################################################### @@ -335,10 +369,10 @@ class NanoVNASaver(QtWidgets.QWidget): self.tdr_result_label = QtWidgets.QLabel() self.tdr_result_label.setMinimumHeight(20) tdr_control_layout.addRow( - "Estimated cable length:", self.tdr_result_label) + "Estimated cable length:", self.tdr_result_label + ) - self.tdr_button = QtWidgets.QPushButton( - "Time Domain Reflectometry ...") + self.tdr_button = QtWidgets.QPushButton("Time Domain Reflectometry ...") self.tdr_button.setMinimumHeight(20) self.tdr_button.clicked.connect(lambda: self.display_window("tdr")) @@ -351,8 +385,13 @@ class NanoVNASaver(QtWidgets.QWidget): ############################################################### left_column.addSpacerItem( - QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Fixed, - QtWidgets.QSizePolicy.Expanding)) + QtWidgets.QSpacerItem( + 1, + 1, + QtWidgets.QSizePolicy.Fixed, + QtWidgets.QSizePolicy.Expanding, + ) + ) ############################################################### # Reference control @@ -390,7 +429,8 @@ class NanoVNASaver(QtWidgets.QWidget): btnOpenCalibrationWindow.setMinimumHeight(20) self.calibrationWindow = CalibrationWindow(self) btnOpenCalibrationWindow.clicked.connect( - lambda: self.display_window("calibration")) + lambda: self.display_window("calibration") + ) ############################################################### # Display setup @@ -399,22 +439,21 @@ class NanoVNASaver(QtWidgets.QWidget): btn_display_setup = QtWidgets.QPushButton("Display setup ...") btn_display_setup.setMinimumHeight(20) btn_display_setup.setMaximumWidth(240) - btn_display_setup.clicked.connect( - lambda: self.display_window("setup")) + btn_display_setup.clicked.connect(lambda: self.display_window("setup")) btn_about = QtWidgets.QPushButton("About ...") btn_about.setMinimumHeight(20) btn_about.setMaximumWidth(240) - btn_about.clicked.connect( - lambda: self.display_window("about")) + btn_about.clicked.connect(lambda: self.display_window("about")) btn_open_file_window = QtWidgets.QPushButton("Files") btn_open_file_window.setMinimumHeight(20) btn_open_file_window.setMaximumWidth(240) btn_open_file_window.clicked.connect( - lambda: self.display_window("file")) + lambda: self.display_window("file") + ) button_grid = QtWidgets.QGridLayout() button_grid.addWidget(btn_open_file_window, 0, 0) @@ -484,8 +523,7 @@ class NanoVNASaver(QtWidgets.QWidget): m2 = Marker("Reference") m2.location = self.markers[0].location m2.resetLabels() - m2.updateLabels(self.ref_data.s11, - self.ref_data.s21) + m2.updateLabels(self.ref_data.s11, self.ref_data.s21) else: logger.warning("No reference data for marker") @@ -525,7 +563,8 @@ class NanoVNASaver(QtWidgets.QWidget): min_vswr = min(s11, key=lambda data: data.vswr) self.s11_min_swr_label.setText( f"{format_vswr(min_vswr.vswr)} @" - f" {format_frequency(min_vswr.freq)}") + f" {format_frequency(min_vswr.freq)}" + ) self.s11_min_rl_label.setText(format_gain(min_vswr.gain)) else: self.s11_min_swr_label.setText("") @@ -536,10 +575,12 @@ class NanoVNASaver(QtWidgets.QWidget): max_gain = max(s21, key=lambda data: data.gain) self.s21_min_gain_label.setText( f"{format_gain(min_gain.gain)}" - f" @ {format_frequency(min_gain.freq)}") + f" @ {format_frequency(min_gain.freq)}" + ) self.s21_max_gain_label.setText( f"{format_gain(max_gain.gain)}" - f" @ {format_frequency(max_gain.freq)}") + f" @ {format_frequency(max_gain.freq)}" + ) else: self.s21_min_gain_label.setText("") self.s21_max_gain_label.setText("") @@ -551,8 +592,7 @@ class NanoVNASaver(QtWidgets.QWidget): self._sweep_control(start=False) for marker in self.markers: - marker.frequencyInput.textEdited.emit( - marker.frequencyInput.text()) + marker.frequencyInput.textEdited.emit(marker.frequencyInput.text()) def setReference(self, s11=None, s21=None, source=None): if not s11: @@ -581,11 +621,13 @@ class NanoVNASaver(QtWidgets.QWidget): if self.sweepSource != "": insert += ( f"Sweep: {self.sweepSource} @ {len(self.data.s11)} points" - f"{', ' if self.referenceSource else ''}") + f"{', ' if self.referenceSource else ''}" + ) if self.referenceSource != "": insert += ( f"Reference: {self.referenceSource} @" - f" {len(self.ref_data.s11)} points") + f" {len(self.ref_data.s11)} points" + ) insert += ")" title = f"{self.baseTitle} {insert or ''}" self.setWindowTitle(title) @@ -612,7 +654,7 @@ class NanoVNASaver(QtWidgets.QWidget): self.showError(self.worker.error_message) with contextlib.suppress(IOError): self.vna.flushSerialBuffers() # Remove any left-over data - self.vna.reconnect() # try reconnection + self.vna.reconnect() # try reconnection self.sweepFinished() def popoutChart(self, chart: Chart): @@ -661,8 +703,12 @@ class NanoVNASaver(QtWidgets.QWidget): new_width = qf_new.horizontalAdvance(standard_string) old_width = qf_normal.horizontalAdvance(standard_string) self.scaleFactor = new_width / old_width - logger.debug("New font width: %f, normal font: %f, factor: %f", - new_width, old_width, self.scaleFactor) + logger.debug( + "New font width: %f, normal font: %f, factor: %f", + new_width, + old_width, + self.scaleFactor, + ) # TODO: Update all the fixed widths to account for the scaling for m in self.markers: m.get_data_layout().setFont(font) diff --git a/src/NanoVNASaver/RFTools.py b/src/NanoVNASaver/RFTools.py index ca12040..7dfcf27 100644 --- a/src/NanoVNASaver/RFTools.py +++ b/src/NanoVNASaver/RFTools.py @@ -34,12 +34,12 @@ class Datapoint(NamedTuple): @property def z(self) -> complex: - """ return the s value complex number """ + """return the s value complex number""" return complex(self.re, self.im) @property def phase(self) -> float: - """ return the datapoint's phase value """ + """return the datapoint's phase value""" return cmath.phase(self.z) @property @@ -77,11 +77,11 @@ class Datapoint(NamedTuple): def capacitiveEquivalent(self, ref_impedance: float = 50) -> float: return impedance_to_capacitance( - self.impedance(ref_impedance), self.freq) + self.impedance(ref_impedance), self.freq + ) def inductiveEquivalent(self, ref_impedance: float = 50) -> float: - return impedance_to_inductance( - self.impedance(ref_impedance), self.freq) + return impedance_to_inductance(self.impedance(ref_impedance), self.freq) def gamma_to_impedance(gamma: complex, ref_impedance: float = 50) -> complex: @@ -124,9 +124,10 @@ def norm_to_impedance(z: complex, ref_impedance: float = 50) -> complex: def parallel_to_serial(z: complex) -> complex: """Convert parallel impedance to serial impedance equivalent""" - z_sq_sum = z.real ** 2 + z.imag ** 2 or 10.0e-30 - return complex(z.real * z.imag ** 2 / z_sq_sum, - z.real ** 2 * z.imag / z_sq_sum) + z_sq_sum = z.real**2 + z.imag**2 or 10.0e-30 + return complex( + z.real * z.imag**2 / z_sq_sum, z.real**2 * z.imag / z_sq_sum + ) def reflection_coefficient(z: complex, ref_impedance: float = 50) -> complex: @@ -136,7 +137,7 @@ def reflection_coefficient(z: complex, ref_impedance: float = 50) -> complex: def serial_to_parallel(z: complex) -> complex: """Convert serial impedance to parallel impedance equivalent""" - z_sq_sum = z.real ** 2 + z.imag ** 2 + z_sq_sum = z.real**2 + z.imag**2 if z.real == 0 and z.imag == 0: return complex(math.inf, math.inf) if z.imag == 0: @@ -150,7 +151,7 @@ def corr_att_data(data: List[Datapoint], att: float) -> List[Datapoint]: """Correct the ratio for a given attenuation on s21 input""" if att <= 0: return data - att = 10**(att / 20) + att = 10 ** (att / 20) ndata = [] for dp in data: corrected = dp.z * att diff --git a/src/NanoVNASaver/SITools.py b/src/NanoVNASaver/SITools.py index 233cfa6..7749650 100644 --- a/src/NanoVNASaver/SITools.py +++ b/src/NanoVNASaver/SITools.py @@ -22,8 +22,29 @@ from decimal import Context, Decimal, InvalidOperation from typing import NamedTuple from numbers import Number, Real -PREFIXES = ("q", "r", "y", "z", "a", "f", "p", "n", "µ", "m", - "", "k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q") +PREFIXES = ( + "q", + "r", + "y", + "z", + "a", + "f", + "p", + "n", + "µ", + "m", + "", + "k", + "M", + "G", + "T", + "P", + "E", + "Z", + "Y", + "R", + "Q", +) def clamp_value(value: Real, rmin: Real, rmax: Real) -> Real: @@ -32,17 +53,17 @@ def clamp_value(value: Real, rmin: Real, rmax: Real) -> Real: def round_ceil(value: Real, digits: int = 0) -> Real: - factor = 10 ** -digits + factor = 10**-digits return factor * math.ceil(value / factor) def round_floor(value: Real, digits: int = 0) -> Real: - factor = 10 ** -digits + factor = 10**-digits return factor * math.floor(value / factor) def log_floor_125(x: float) -> float: - log_base = 10**(math.floor(math.log10(x))) + log_base = 10 ** (math.floor(math.log10(x))) log_factor = x / log_base if log_factor >= 5: return 5 * log_base @@ -80,31 +101,44 @@ class Value: self.fmt = fmt if isinstance(value, str): self._value = Decimal(math.nan) - if value.lower() != 'nan': + if value.lower() != "nan": self.parse(value) else: self._value = Decimal(value, context=Value.CTX) def __repr__(self) -> str: - return (f"{self.__class__.__name__}(" - f"{repr(self._value)}, '{self._unit}', {self.fmt})") + return ( + f"{self.__class__.__name__}(" + f"{repr(self._value)}, '{self._unit}', {self.fmt})" + ) def __str__(self) -> str: fmt = self.fmt if math.isnan(self._value): return f"-{fmt.space_str}{self._unit}" - if (fmt.assume_infinity and - abs(self._value) >= 10 ** ((fmt.max_offset + 1) * 3)): - return (("-" if self._value < 0 else "") + - "\N{INFINITY}" + fmt.space_str + self._unit) + if fmt.assume_infinity and abs(self._value) >= 10 ** ( + (fmt.max_offset + 1) * 3 + ): + return ( + ("-" if self._value < 0 else "") + + "\N{INFINITY}" + + fmt.space_str + + self._unit + ) if self._value < fmt.printable_min: return fmt.unprintable_under + self._unit if self._value > fmt.printable_max: return fmt.unprintable_over + self._unit - offset = clamp_value( - int(math.log10(abs(self._value)) // 3), - fmt.min_offset, fmt.max_offset) if self._value else 0 + offset = ( + clamp_value( + int(math.log10(abs(self._value)) // 3), + fmt.min_offset, + fmt.max_offset, + ) + if self._value + else 0 + ) real = float(self._value) / (10 ** (offset * 3)) @@ -112,8 +146,9 @@ class Value: formstr = ".0f" else: max_digits = fmt.max_nr_digits + ( - (1 if not fmt.fix_decimals and abs(real) < 10 else 0) + - (1 if not fmt.fix_decimals and abs(real) < 100 else 0)) + (1 if not fmt.fix_decimals and abs(real) < 10 else 0) + + (1 if not fmt.fix_decimals and abs(real) < 100 else 0) + ) formstr = f".{max_digits - 3}f" if self.fmt.allways_signed: @@ -150,10 +185,13 @@ class Value: value = value.replace(" ", "") # Ignore spaces if self._unit and ( - value.endswith(self._unit) or - (self.fmt.parse_sloppy_unit and - value.lower().endswith(self._unit.lower()))): # strip unit - value = value[:-len(self._unit)] + value.endswith(self._unit) + or ( + self.fmt.parse_sloppy_unit + and value.lower().endswith(self._unit.lower()) + ) + ): # strip unit + value = value[: -len(self._unit)] factor = 1 # fix for e.g. KHz, mHz gHz as milli-Hertz mostly makes no @@ -170,13 +208,14 @@ class Value: self._value = -math.inf else: try: - self._value = (Decimal(value, context=Value.CTX) - * Decimal(factor, context=Value.CTX)) + self._value = Decimal(value, context=Value.CTX) * Decimal( + factor, context=Value.CTX + ) except InvalidOperation as exc: raise ValueError() from exc - self._value = clamp_value(self._value, - self.fmt.parse_clamp_min, - self.fmt.parse_clamp_max) + self._value = clamp_value( + self._value, self.fmt.parse_clamp_min, self.fmt.parse_clamp_max + ) return self @property diff --git a/src/NanoVNASaver/Settings/Bands.py b/src/NanoVNASaver/Settings/Bands.py index 1e73206..fd18d5b 100644 --- a/src/NanoVNASaver/Settings/Bands.py +++ b/src/NanoVNASaver/Settings/Bands.py @@ -57,9 +57,12 @@ class BandsModel(QtCore.QAbstractTableModel): # These bands correspond broadly to the Danish Amateur Radio allocation def __init__(self): super().__init__() - self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat, - QtCore.QSettings.UserScope, - "NanoVNASaver", "Bands") + self.settings = QtCore.QSettings( + QtCore.QSettings.IniFormat, + QtCore.QSettings.UserScope, + "NanoVNASaver", + "Bands", + ) self.settings.setIniCodec("UTF-8") self.enabled = self.settings.value("ShowBands", False, bool) @@ -71,7 +74,8 @@ class BandsModel(QtCore.QAbstractTableModel): def saveSettings(self): self.settings.setValue( "bands", - [f"{name};{start};{end}" for name, start, end in self.bands]) + [f"{name};{start};{end}" for name, start, end in self.bands], + ) self.settings.sync() def resetBands(self): @@ -87,18 +91,22 @@ class BandsModel(QtCore.QAbstractTableModel): def data(self, index: QModelIndex, role: int = ...) -> QtCore.QVariant: if role in [ - QtCore.Qt.DisplayRole, QtCore.Qt.ItemDataRole, QtCore.Qt.EditRole, + QtCore.Qt.DisplayRole, + QtCore.Qt.ItemDataRole, + QtCore.Qt.EditRole, ]: return QtCore.QVariant(self.bands[index.row()][index.column()]) if role == QtCore.Qt.TextAlignmentRole: if index.column() == 0: return QtCore.QVariant(QtCore.Qt.AlignCenter) return QtCore.QVariant( - QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) + QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter + ) return QtCore.QVariant() - def setData(self, index: QModelIndex, - value: typing.Any, role: int = ...) -> bool: + def setData( + self, index: QModelIndex, value: typing.Any, role: int = ... + ) -> bool: if role == QtCore.Qt.EditRole and index.isValid(): t = self.bands[index.row()] name = t[0] @@ -116,14 +124,14 @@ class BandsModel(QtCore.QAbstractTableModel): return True return False - def index(self, row: int, - column: int, _: QModelIndex = ...) -> QModelIndex: + def index(self, row: int, column: int, _: QModelIndex = ...) -> QModelIndex: return self.createIndex(row, column) def addRow(self): self.bands.append(("New", 0, 0)) - self.dataChanged.emit(self.index(len(self.bands), 0), - self.index(len(self.bands), 2)) + self.dataChanged.emit( + self.index(len(self.bands), 0), self.index(len(self.bands), 2) + ) self.layoutChanged.emit() def removeRow(self, row: int, _: QModelIndex = ...) -> bool: @@ -132,10 +140,13 @@ class BandsModel(QtCore.QAbstractTableModel): self.saveSettings() return True - def headerData(self, section: int, - orientation: QtCore.Qt.Orientation, role: int = ...): - if (role == QtCore.Qt.DisplayRole and - orientation == QtCore.Qt.Horizontal): + def headerData( + self, section: int, orientation: QtCore.Qt.Orientation, role: int = ... + ): + if ( + role == QtCore.Qt.DisplayRole + and orientation == QtCore.Qt.Horizontal + ): with contextlib.suppress(IndexError): return _HEADER_DATA[section] return None @@ -143,9 +154,10 @@ class BandsModel(QtCore.QAbstractTableModel): def flags(self, index: QModelIndex) -> QtCore.Qt.ItemFlags: if index.isValid(): return QtCore.Qt.ItemFlags( - QtCore.Qt.ItemIsEditable | - QtCore.Qt.ItemIsEnabled | - QtCore.Qt.ItemIsSelectable) + QtCore.Qt.ItemIsEditable + | QtCore.Qt.ItemIsEnabled + | QtCore.Qt.ItemIsSelectable + ) super().flags(index) def setColor(self, color): diff --git a/src/NanoVNASaver/Settings/Sweep.py b/src/NanoVNASaver/Settings/Sweep.py index fa4900c..4f9b44c 100644 --- a/src/NanoVNASaver/Settings/Sweep.py +++ b/src/NanoVNASaver/Settings/Sweep.py @@ -32,10 +32,13 @@ class SweepMode(Enum): class Properties: - def __init__(self, name: str = "", - mode: 'SweepMode' = SweepMode.SINGLE, - averages: Tuple[int, int] = (3, 0), - logarithmic: bool = False): + def __init__( + self, + name: str = "", + mode: "SweepMode" = SweepMode.SINGLE, + averages: Tuple[int, int] = (3, 0), + logarithmic: bool = False, + ): self.name = name self.mode = mode self.averages = averages @@ -44,13 +47,19 @@ class Properties: def __repr__(self): return ( f"Properties('{self.name}', {self.mode}, {self.averages}," - f" {self.logarithmic})") + f" {self.logarithmic})" + ) class Sweep: - def __init__(self, start: int = 3600000, end: int = 30000000, - points: int = 101, segments: int = 1, - properties: 'Properties' = Properties()): + def __init__( + self, + start: int = 3600000, + end: int = 30000000, + points: int = 101, + segments: int = 1, + properties: "Properties" = Properties(), + ): self.start = start self.end = end self.points = points @@ -63,18 +72,22 @@ class Sweep: def __repr__(self) -> str: return ( f"Sweep({self.start}, {self.end}, {self.points}, {self.segments}," - f" {self.properties})") + f" {self.properties})" + ) def __eq__(self, other) -> bool: - return (self.start == other.start and - self.end == other.end and - self.points == other.points and - self.segments == other.segments and - self.properties == other.properties) + return ( + self.start == other.start + and self.end == other.end + and self.points == other.points + and self.segments == other.segments + and self.properties == other.properties + ) - def copy(self) -> 'Sweep': - return Sweep(self.start, self.end, self.points, self.segments, - self.properties) + def copy(self) -> "Sweep": + return Sweep( + self.start, self.end, self.points, self.segments, self.properties + ) @property def span(self) -> int: @@ -86,11 +99,11 @@ class Sweep: def check(self): if ( - self.segments <= 0 - or self.points <= 0 - or self.start <= 0 - or self.end <= 0 - or self.stepsize < 1 + self.segments <= 0 + or self.points <= 0 + or self.start <= 0 + or self.end <= 0 + or self.stepsize < 1 ): raise ValueError(f"Illegal sweep settings: {self}") diff --git a/src/NanoVNASaver/SweepWorker.py b/src/NanoVNASaver/SweepWorker.py index 8f3e531..e06b04d 100644 --- a/src/NanoVNASaver/SweepWorker.py +++ b/src/NanoVNASaver/SweepWorker.py @@ -42,9 +42,8 @@ def truncate(values: List[List[Tuple]], count: int) -> List[List[Tuple]]: for valueset in np.swapaxes(values, 0, 1).tolist(): avg = complex(*np.average(valueset, 0)) truncated.append( - sorted(valueset, - key=lambda v, a=avg: - abs(a - complex(*v)))[:keep]) + sorted(valueset, key=lambda v, a=avg: abs(a - complex(*v)))[:keep] + ) return np.swapaxes(truncated, 0, 1).tolist() @@ -87,7 +86,8 @@ class SweepWorker(QtCore.QRunnable): logger.info("Initializing SweepWorker") if not self.app.vna.connected(): logger.debug( - "Attempted to run without being connected to the NanoVNA") + "Attempted to run without being connected to the NanoVNA" + ) self.running = False return @@ -106,8 +106,9 @@ class SweepWorker(QtCore.QRunnable): if sweep.segments > 1: start = sweep.start end = sweep.end - logger.debug("Resetting NanoVNA sweep to full range: %d to %d", - start, end) + logger.debug( + "Resetting NanoVNA sweep to full range: %d to %d", start, end + ) self.app.vna.resetSweep(start, end) self.percentage = 100 @@ -117,9 +118,11 @@ class SweepWorker(QtCore.QRunnable): def _run_loop(self) -> None: sweep = self.sweep - averages = (sweep.properties.averages[0] - if sweep.properties.mode == SweepMode.AVERAGE - else 1) + averages = ( + sweep.properties.averages[0] + if sweep.properties.mode == SweepMode.AVERAGE + else 1 + ) logger.info("%d averages", averages) while True: @@ -131,7 +134,8 @@ class SweepWorker(QtCore.QRunnable): start, stop = sweep.get_index_range(i) freq, values11, values21 = self.readAveragedSegment( - start, stop, averages) + start, stop, averages + ) self.percentage = (i + 1) * 100 / sweep.segments self.updateData(freq, values11, values21, i) if sweep.properties.mode != SweepMode.CONTINOUS or self.stopped: @@ -152,14 +156,18 @@ class SweepWorker(QtCore.QRunnable): def updateData(self, frequencies, values11, values21, index): # Update the data from (i*101) to (i+1)*101 logger.debug( - "Calculating data and inserting in existing data at index %d", - index) + "Calculating data and inserting in existing data at index %d", index + ) offset = self.sweep.points * index - raw_data11 = [Datapoint(freq, values11[i][0], values11[i][1]) - for i, freq in enumerate(frequencies)] - raw_data21 = [Datapoint(freq, values21[i][0], values21[i][1]) - for i, freq in enumerate(frequencies)] + raw_data11 = [ + Datapoint(freq, values11[i][0], values11[i][1]) + for i, freq in enumerate(frequencies) + ] + raw_data21 = [ + Datapoint(freq, values21[i][0], values21[i][1]) + for i, freq in enumerate(frequencies) + ] data11, data21 = self.applyCalibration(raw_data11, raw_data21) logger.debug("update Freqs: %s, Offset: %s", len(frequencies), offset) @@ -169,16 +177,18 @@ class SweepWorker(QtCore.QRunnable): self.rawData11[offset + i] = raw_data11[i] self.rawData21[offset + i] = raw_data21[i] - logger.debug("Saving data to application (%d and %d points)", - len(self.data11), len(self.data21)) + logger.debug( + "Saving data to application (%d and %d points)", + len(self.data11), + len(self.data21), + ) self.app.saveData(self.data11, self.data21) logger.debug('Sending "updated" signal') self.signals.updated.emit() - def applyCalibration(self, - raw_data11: List[Datapoint], - raw_data21: List[Datapoint] - ) -> Tuple[List[Datapoint], List[Datapoint]]: + def applyCalibration( + self, raw_data11: List[Datapoint], raw_data21: List[Datapoint] + ) -> Tuple[List[Datapoint], List[Datapoint]]: data11: List[Datapoint] = [] data21: List[Datapoint] = [] @@ -186,8 +196,9 @@ class SweepWorker(QtCore.QRunnable): data11 = raw_data11.copy() data21 = raw_data21.copy() elif self.app.calibration.isValid1Port(): - data11.extend(self.app.calibration.correct11(dp) - for dp in raw_data11) + data11.extend( + self.app.calibration.correct11(dp) for dp in raw_data11 + ) else: data11 = raw_data11.copy() @@ -199,8 +210,10 @@ class SweepWorker(QtCore.QRunnable): data21 = raw_data21 if self.offsetDelay != 0: - data11 = [correct_delay(dp, self.offsetDelay, reflect=True) - for dp in data11] + data11 = [ + correct_delay(dp, self.offsetDelay, reflect=True) + for dp in data11 + ] data21 = [correct_delay(dp, self.offsetDelay) for dp in data21] return data11, data21 @@ -209,8 +222,9 @@ class SweepWorker(QtCore.QRunnable): values11 = [] values21 = [] freq = [] - logger.info("Reading from %d to %d. Averaging %d values", - start, stop, averages) + logger.info( + "Reading from %d to %d. Averaging %d values", start, stop, averages + ) for i in range(averages): if self.stopped: logger.debug("Stopping averaging as signalled.") @@ -227,8 +241,9 @@ class SweepWorker(QtCore.QRunnable): retry += 1 freq, tmp11, tmp21 = self.readSegment(start, stop) if retry > 1: - logger.error("retry %s readSegment(%s,%s)", - retry, start, stop) + logger.error( + "retry %s readSegment(%s,%s)", retry, start, stop + ) sleep(0.5) values11.append(tmp11) values21.append(tmp21) @@ -240,8 +255,7 @@ class SweepWorker(QtCore.QRunnable): truncates = self.sweep.properties.averages[1] if truncates > 0 and averages > 1: - logger.debug("Truncating %d values by %d", - len(values11), truncates) + logger.debug("Truncating %d values by %d", len(values11), truncates) values11 = truncate(values11, truncates) values21 = truncate(values21, truncates) @@ -278,36 +292,42 @@ class SweepWorker(QtCore.QRunnable): a, b = d.split(" ") try: if self.app.vna.validateInput and ( - abs(float(a)) > 9.5 or - abs(float(b)) > 9.5): + abs(float(a)) > 9.5 or abs(float(b)) > 9.5 + ): logger.warning( - "Got a non plausible data value: (%s)", d) + "Got a non plausible data value: (%s)", d + ) done = False break returndata.append((float(a), float(b))) except ValueError as exc: - logger.exception("An exception occurred reading %s: %s", - data, exc) + logger.exception( + "An exception occurred reading %s: %s", data, exc + ) done = False if not done: logger.debug("Re-reading %s", data) sleep(0.2) count += 1 if count == 5: - logger.error("Tried and failed to read %s %d times.", - data, count) + logger.error( + "Tried and failed to read %s %d times.", data, count + ) logger.debug("trying to reconnect") self.app.vna.reconnect() if count >= 10: logger.critical( "Tried and failed to read %s %d times. Giving up.", - data, count) + data, + count, + ) raise IOError( f"Failed reading {data} {count} times.\n" f"Data outside expected valid ranges," f" or in an unexpected format.\n\n" f"You can disable data validation on the" - f"device settings screen.") + f"device settings screen." + ) return returndata def gui_error(self, message: str): diff --git a/src/NanoVNASaver/Touchstone.py b/src/NanoVNASaver/Touchstone.py index eba4d3c..2a9a7f9 100644 --- a/src/NanoVNASaver/Touchstone.py +++ b/src/NanoVNASaver/Touchstone.py @@ -35,20 +35,22 @@ class Options: # Fun fact: In Touchstone 1.1 spec all params are optional unordered. # Just the line has to start with "#" UNIT_TO_FACTOR = { - "ghz": 10 ** 9, - "mhz": 10 ** 6, - "khz": 10 ** 3, - "hz": 10 ** 0, + "ghz": 10**9, + "mhz": 10**6, + "khz": 10**3, + "hz": 10**0, } VALID_UNITS = UNIT_TO_FACTOR.keys() VALID_PARAMETERS = "syzgh" VALID_FORMATS = ("ma", "db", "ri") - def __init__(self, - unit: str = "GHZ", - parameter: str = "S", - t_format: str = "ma", - resistance: int = 50): + def __init__( + self, + unit: str = "GHZ", + parameter: str = "S", + t_format: str = "ma", + resistance: int = 50, + ): # set defaults assert unit.lower() in Options.VALID_UNITS assert parameter.lower() in Options.VALID_PARAMETERS @@ -145,9 +147,11 @@ class Touchstone: return self.sdata[Touchstone.FIELD_ORDER.index(name)] def s_freq(self, name: str, freq: int) -> Datapoint: - return Datapoint(freq, - float(self._interp[name]["real"](freq)), - float(self._interp[name]["imag"](freq))) + return Datapoint( + freq, + float(self._interp[name]["real"](freq)), + float(self._interp[name]["imag"](freq)), + ) def swap(self): self.sdata = [self.s22, self.s12, self.s21, self.s11] @@ -170,12 +174,20 @@ class Touchstone: imag.append(dp.im) self._interp[i] = { - "real": interp1d(freq, real, - kind="slinear", bounds_error=False, - fill_value=(real[0], real[-1])), - "imag": interp1d(freq, imag, - kind="slinear", bounds_error=False, - fill_value=(imag[0], imag[-1])), + "real": interp1d( + freq, + real, + kind="slinear", + bounds_error=False, + fill_value=(real[0], real[-1]), + ), + "imag": interp1d( + freq, + imag, + kind="slinear", + bounds_error=False, + fill_value=(imag[0], imag[-1]), + ), } def _parse_comments(self, fp) -> str: @@ -192,27 +204,29 @@ class Touchstone: vals = iter(data) for v in vals: if self.opts.format == "ri": - next(data_list).append(Datapoint(freq, float(v), - float(next(vals)))) + next(data_list).append( + Datapoint(freq, float(v), float(next(vals))) + ) if self.opts.format == "ma": z = cmath.rect(float(v), math.radians(float(next(vals)))) next(data_list).append(Datapoint(freq, z.real, z.imag)) if self.opts.format == "db": - z = cmath.rect(10 ** (float(v) / 20), - math.radians(float(next(vals)))) + z = cmath.rect( + 10 ** (float(v) / 20), math.radians(float(next(vals))) + ) next(data_list).append(Datapoint(freq, z.real, z.imag)) def load(self): logger.info("Attempting to open file %s", self.filename) try: - with open(self.filename, encoding='utf-8') as infile: + with open(self.filename, encoding="utf-8") as infile: self.loads(infile.read()) except IOError as e: logger.exception("Failed to open %s: %s", self.filename, e) def loads(self, s: str): """Parse touchstone 1.1 string input - appends to existing sdata if Touchstone object exists + appends to existing sdata if Touchstone object exists """ try: self._loads(s) @@ -239,7 +253,7 @@ class Touchstone: continue # ignore comments at data end - data = line.split('!')[0] + data = line.split("!")[0] data = data.split() freq, data = round(float(data[0]) * self.opts.factor), data[1:] data_len = len(data) @@ -270,8 +284,7 @@ class Touchstone: nr_params: Number of s-parameters. 2 for s1p, 4 for s2p """ - logger.info("Attempting to open file %s for writing", - self.filename) + logger.info("Attempting to open file %s for writing", self.filename) with open(self.filename, "w", encoding="utf-8") as outfile: outfile.write(self.saves(nr_params)) diff --git a/src/NanoVNASaver/Version.py b/src/NanoVNASaver/Version.py index 84b4c37..f6bf3e2 100644 --- a/src/NanoVNASaver/Version.py +++ b/src/NanoVNASaver/Version.py @@ -22,13 +22,16 @@ logger = logging.getLogger(__name__) class Version: - RXP = re.compile(r"""^ + RXP = re.compile( + r"""^ \D* (?P\d+)\. (?P\d+)\.? (?P\d+)? (?P.*) - $""", re.VERBOSE) + $""", + re.VERBOSE, + ) def __init__(self, vstring: str = "0.0.0"): self.data = { @@ -68,8 +71,10 @@ class Version: return self.data == other.data def __str__(self) -> str: - return (f'{self.data["major"]}.{self.data["minor"]}' - f'.{self.data["revision"]}{self.data["note"]}') + return ( + f'{self.data["major"]}.{self.data["minor"]}' + f'.{self.data["revision"]}{self.data["note"]}' + ) @property def major(self) -> int: diff --git a/src/NanoVNASaver/Windows/About.py b/src/NanoVNASaver/Windows/About.py index 1b1d8bc..e65bedd 100644 --- a/src/NanoVNASaver/Windows/About.py +++ b/src/NanoVNASaver/Windows/About.py @@ -53,28 +53,36 @@ class AboutWindow(QtWidgets.QWidget): layout = QtWidgets.QVBoxLayout() top_layout.addLayout(layout) - layout.addWidget(QtWidgets.QLabel( - f"NanoVNASaver version {self.app.version}")) + layout.addWidget( + QtWidgets.QLabel(f"NanoVNASaver version {self.app.version}") + ) layout.addWidget(QtWidgets.QLabel("")) - layout.addWidget(QtWidgets.QLabel( - "\N{COPYRIGHT SIGN} Copyright 2019, 2020 Rune B. Broberg\n" - "\N{COPYRIGHT SIGN} Copyright 2020ff NanoVNA-Saver Authors" - )) - layout.addWidget(QtWidgets.QLabel( - "This program comes with ABSOLUTELY NO WARRANTY")) - layout.addWidget(QtWidgets.QLabel( - "This program is licensed under the" - " GNU General Public License version 3")) + layout.addWidget( + QtWidgets.QLabel( + "\N{COPYRIGHT SIGN} Copyright 2019, 2020 Rune B. Broberg\n" + "\N{COPYRIGHT SIGN} Copyright 2020ff NanoVNA-Saver Authors" + ) + ) + layout.addWidget( + QtWidgets.QLabel("This program comes with ABSOLUTELY NO WARRANTY") + ) + layout.addWidget( + QtWidgets.QLabel( + "This program is licensed under the" + " GNU General Public License version 3" + ) + ) layout.addWidget(QtWidgets.QLabel("")) link_label = QtWidgets.QLabel( - f'For further details, see: ' - f"{INFO_URL}") + f'For further details, see: ' f"{INFO_URL}" + ) link_label.setOpenExternalLinks(True) layout.addWidget(link_label) layout.addWidget(QtWidgets.QLabel("")) self.versionLabel = QtWidgets.QLabel( - "NanoVNA Firmware Version: Not connected.") + "NanoVNA Firmware Version: Not connected." + ) layout.addWidget(self.versionLabel) layout.addStretch() @@ -106,14 +114,15 @@ class AboutWindow(QtWidgets.QWidget): with contextlib.suppress(IOError, AttributeError): self.versionLabel.setText( f"NanoVNA Firmware Version: {self.app.vna.name} " - f"v{self.app.vna.version}") + f"v{self.app.vna.version}" + ) def findUpdates(self, automatic=False): latest_version = Version() latest_url = "" try: req = request.Request(VERSION_URL) - req.add_header('User-Agent', f'NanoVNA-Saver/{self.app.version}') + req.add_header("User-Agent", f"NanoVNA-Saver/{self.app.version}") for line in request.urlopen(req, timeout=3): line = line.decode("utf-8") if line.startswith("VERSION ="): @@ -122,17 +131,20 @@ class AboutWindow(QtWidgets.QWidget): latest_url = line[13:].strip(" \"'") except error.HTTPError as e: logger.exception( - "Checking for updates produced an HTTP exception: %s", e) + "Checking for updates produced an HTTP exception: %s", e + ) self.updateLabel.setText("Connection error.") return except TypeError as e: logger.exception( - "Checking for updates provided an unparseable file: %s", e) + "Checking for updates provided an unparseable file: %s", e + ) self.updateLabel.setText("Data error reading versions.") return except error.URLError as e: logger.exception( - "Checking for updates produced a URL exception: %s", e) + "Checking for updates produced a URL exception: %s", e + ) self.updateLabel.setText("Connection error.") return @@ -147,13 +159,17 @@ class AboutWindow(QtWidgets.QWidget): "Updates available", f"There is a new update for NanoVNA-Saver available!\n" f"Version {latest_version}\n\n" - f'Press "About" to find the update.') + f'Press "About" to find the update.', + ) else: QtWidgets.QMessageBox.information( - self, "Updates available", - "There is a new update for NanoVNA-Saver available!") + self, + "Updates available", + "There is a new update for NanoVNA-Saver available!", + ) self.updateLabel.setText( - f'New version available.') + f'New version available.' + ) self.updateLabel.setOpenExternalLinks(True) else: # Probably don't show a message box, just update the screen? @@ -161,5 +177,6 @@ class AboutWindow(QtWidgets.QWidget): # self.updateLabel.setText( f"Last checked: " - f"{strftime('%Y-%m-%d %H:%M:%S', localtime())}") + f"{strftime('%Y-%m-%d %H:%M:%S', localtime())}" + ) return diff --git a/src/NanoVNASaver/Windows/AnalysisWindow.py b/src/NanoVNASaver/Windows/AnalysisWindow.py index 906dc47..b467a0a 100644 --- a/src/NanoVNASaver/Windows/AnalysisWindow.py +++ b/src/NanoVNASaver/Windows/AnalysisWindow.py @@ -29,7 +29,9 @@ from NanoVNASaver.Analysis.HighPassAnalysis import HighPassAnalysis from NanoVNASaver.Analysis.LowPassAnalysis import LowPassAnalysis from NanoVNASaver.Analysis.PeakSearchAnalysis import PeakSearchAnalysis from NanoVNASaver.Analysis.ResonanceAnalysis import ResonanceAnalysis -from NanoVNASaver.Analysis.SimplePeakSearchAnalysis import SimplePeakSearchAnalysis +from NanoVNASaver.Analysis.SimplePeakSearchAnalysis import ( + SimplePeakSearchAnalysis, +) from NanoVNASaver.Analysis.VSWRAnalysis import VSWRAnalysis from NanoVNASaver.Windows.Defaults import make_scrollable @@ -55,25 +57,28 @@ 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( - "Low-pass filter", LowPassAnalysis(self.app)) + "Band-pass filter", BandPassAnalysis(self.app) + ) self.analysis_list.addItem( - "Band-pass filter", BandPassAnalysis(self.app)) + "High-pass filter", HighPassAnalysis(self.app) + ) self.analysis_list.addItem( - "High-pass filter", HighPassAnalysis(self.app)) + "Band-stop filter", BandStopAnalysis(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)) + "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( - "Resonance analysis", ResonanceAnalysis(self.app)) + "Resonance analysis", ResonanceAnalysis(self.app) + ) + self.analysis_list.addItem("HWEF analysis", EFHWAnalysis(self.app)) self.analysis_list.addItem( - "HWEF analysis", EFHWAnalysis(self.app)) - self.analysis_list.addItem( - "MagLoop analysis", MagLoopAnalysis(self.app)) + "MagLoop analysis", MagLoopAnalysis(self.app) + ) select_analysis_layout.addRow("Analysis type", self.analysis_list) self.analysis_list.currentIndexChanged.connect(self.updateSelection) @@ -82,15 +87,18 @@ class AnalysisWindow(QtWidgets.QWidget): select_analysis_layout.addRow(btn_run_analysis) self.checkbox_run_automatically = QtWidgets.QCheckBox( - "Run automatically") + "Run automatically" + ) self.checkbox_run_automatically.stateChanged.connect( - self.toggleAutomaticRun) + self.toggleAutomaticRun + ) select_analysis_layout.addRow(self.checkbox_run_automatically) analysis_box = QtWidgets.QGroupBox("Analysis") analysis_box.setSizePolicy( QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding) + QtWidgets.QSizePolicy.MinimumExpanding, + ) self.analysis_layout = QtWidgets.QVBoxLayout(analysis_box) self.analysis_layout.setContentsMargins(0, 0, 0, 0) @@ -110,7 +118,8 @@ class AnalysisWindow(QtWidgets.QWidget): if old_item is not None: old_widget = self.analysis_layout.itemAt(0).widget() self.analysis_layout.replaceWidget( - old_widget, self.analysis.widget()) + old_widget, self.analysis.widget() + ) old_widget.hide() else: self.analysis_layout.addWidget(self.analysis.widget()) diff --git a/src/NanoVNASaver/Windows/Bands.py b/src/NanoVNASaver/Windows/Bands.py index c878842..fb4ded1 100644 --- a/src/NanoVNASaver/Windows/Bands.py +++ b/src/NanoVNASaver/Windows/Bands.py @@ -66,6 +66,7 @@ class BandsWindow(QtWidgets.QWidget): QtWidgets.QMessageBox.Warning, "Confirm reset", "Are you sure you want to reset the bands to default?", - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel).exec() + QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel, + ).exec() if confirm == QtWidgets.QMessageBox.Yes: self.app.bands.resetBands() diff --git a/src/NanoVNASaver/Windows/CalibrationSettings.py b/src/NanoVNASaver/Windows/CalibrationSettings.py index bcde4ab..431b923 100644 --- a/src/NanoVNASaver/Windows/CalibrationSettings.py +++ b/src/NanoVNASaver/Windows/CalibrationSettings.py @@ -50,8 +50,10 @@ class CalibrationWindow(QtWidgets.QWidget): self.setMinimumWidth(450) self.setWindowTitle("Calibration") self.setWindowIcon(self.app.icon) - self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, - QtWidgets.QSizePolicy.MinimumExpanding) + self.setSizePolicy( + QtWidgets.QSizePolicy.MinimumExpanding, + QtWidgets.QSizePolicy.MinimumExpanding, + ) QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) @@ -67,28 +69,38 @@ class CalibrationWindow(QtWidgets.QWidget): calibration_status_layout = QtWidgets.QFormLayout() self.calibration_status_label = QtWidgets.QLabel("Device calibration") self.calibration_source_label = QtWidgets.QLabel("NanoVNA") - calibration_status_layout.addRow("Calibration:", - self.calibration_status_label) - calibration_status_layout.addRow("Source:", - self.calibration_source_label) + calibration_status_layout.addRow( + "Calibration:", self.calibration_status_label + ) + calibration_status_layout.addRow( + "Source:", self.calibration_source_label + ) calibration_status_group.setLayout(calibration_status_layout) left_layout.addWidget(calibration_status_group) calibration_control_group = QtWidgets.QGroupBox("Calibrate") calibration_control_layout = QtWidgets.QFormLayout( - calibration_control_group) + calibration_control_group + ) cal_btn = {} self.cal_label = {} - for label_name in ("short", "open", "load", - "through", "thrurefl", "isolation"): + for label_name in ( + "short", + "open", + "load", + "through", + "thrurefl", + "isolation", + ): self.cal_label[label_name] = QtWidgets.QLabel("Uncalibrated") - cal_btn[label_name] = QtWidgets.QPushButton( - label_name.capitalize()) + cal_btn[label_name] = QtWidgets.QPushButton(label_name.capitalize()) cal_btn[label_name].setMinimumHeight(20) cal_btn[label_name].clicked.connect( - partial(self.manual_save, label_name)) + partial(self.manual_save, label_name) + ) calibration_control_layout.addRow( - cal_btn[label_name], self.cal_label[label_name]) + cal_btn[label_name], self.cal_label[label_name] + ) self.input_offset_delay = QtWidgets.QDoubleSpinBox() self.input_offset_delay.setMinimumHeight(20) @@ -100,7 +112,8 @@ class CalibrationWindow(QtWidgets.QWidget): calibration_control_layout.addRow(QtWidgets.QLabel("")) calibration_control_layout.addRow( - "Offset delay", self.input_offset_delay) + "Offset delay", self.input_offset_delay + ) self.btn_automatic = QtWidgets.QPushButton("Calibration assistant") self.btn_automatic.setMinimumHeight(20) @@ -126,7 +139,8 @@ class CalibrationWindow(QtWidgets.QWidget): calibration_notes_group = QtWidgets.QGroupBox("Notes") calibration_notes_layout = QtWidgets.QVBoxLayout( - calibration_notes_group) + calibration_notes_group + ) self.notes_textedit = QtWidgets.QPlainTextEdit() calibration_notes_layout.addWidget(self.notes_textedit) @@ -225,7 +239,8 @@ class CalibrationWindow(QtWidgets.QWidget): self.cal_standard_save_box = QtWidgets.QGroupBox("Saved settings") cal_standard_save_layout = QtWidgets.QVBoxLayout( - self.cal_standard_save_box) + self.cal_standard_save_box + ) self.cal_standard_save_box.setDisabled(True) self.cal_standard_save_selector = QtWidgets.QComboBox() @@ -253,7 +268,8 @@ class CalibrationWindow(QtWidgets.QWidget): def checkExpertUser(self): if not self.app.settings.value("ExpertCalibrationUser", False, bool): response = QtWidgets.QMessageBox.question( - self, "Are you sure?", + self, + "Are you sure?", ( "Use of the manual calibration buttons is non-intuitive," " and primarily suited for users with very specialized" @@ -267,7 +283,8 @@ class CalibrationWindow(QtWidgets.QWidget): " Yes." ), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel, - QtWidgets.QMessageBox.Cancel) + QtWidgets.QMessageBox.Cancel, + ) if response == QtWidgets.QMessageBox.Yes: self.app.settings.setValue("ExpertCalibrationUser", True) @@ -280,8 +297,7 @@ class CalibrationWindow(QtWidgets.QWidget): self.app.calibration.insert(name, self.app.data.s21) else: self.app.calibration.insert(name, self.app.data.s11) - self.cal_label[name].setText( - _format_cal_label(len(self.app.data.s11))) + self.cal_label[name].setText(_format_cal_label(len(self.app.data.s11))) def manual_save(self, name: str): if self.checkExpertUser(): @@ -289,8 +305,7 @@ class CalibrationWindow(QtWidgets.QWidget): def listCalibrationStandards(self): self.cal_standard_save_selector.clear() - num_standards = self.app.settings.beginReadArray( - "CalibrationStandards") + num_standards = self.app.settings.beginReadArray("CalibrationStandards") for i in range(num_standards): self.app.settings.setArrayIndex(i) name = self.app.settings.value("Name", defaultValue="INVALID NAME") @@ -300,15 +315,15 @@ class CalibrationWindow(QtWidgets.QWidget): self.cal_standard_save_selector.setCurrentText("New") def saveCalibrationStandard(self): - num_standards = self.app.settings.beginReadArray( - "CalibrationStandards") + num_standards = self.app.settings.beginReadArray("CalibrationStandards") self.app.settings.endArray() if self.cal_standard_save_selector.currentData() == -1: # New cal standard # Get a name name, selected = QtWidgets.QInputDialog.getText( - self, "Calibration standard name", "Enter name to save as") + self, "Calibration standard name", "Enter name to save as" + ) if not selected or not name: return write_num = num_standards @@ -317,8 +332,7 @@ class CalibrationWindow(QtWidgets.QWidget): write_num = self.cal_standard_save_selector.currentData() name = self.cal_standard_save_selector.currentText() - self.app.settings.beginWriteArray( - "CalibrationStandards", num_standards) + self.app.settings.beginWriteArray("CalibrationStandards", num_standards) self.app.settings.setArrayIndex(write_num) self.app.settings.setValue("Name", name) @@ -361,8 +375,7 @@ class CalibrationWindow(QtWidgets.QWidget): self.short_l1_input.setText(str(self.app.settings.value("ShortL1", 0))) self.short_l2_input.setText(str(self.app.settings.value("ShortL2", 0))) self.short_l3_input.setText(str(self.app.settings.value("ShortL3", 0))) - self.short_length.setText( - str(self.app.settings.value("ShortDelay", 0))) + self.short_length.setText(str(self.app.settings.value("ShortDelay", 0))) self.open_c0_input.setText(str(self.app.settings.value("OpenC0", 50))) self.open_c1_input.setText(str(self.app.settings.value("OpenC1", 0))) @@ -376,7 +389,8 @@ class CalibrationWindow(QtWidgets.QWidget): self.load_length.setText(str(self.app.settings.value("LoadDelay", 0))) self.through_length.setText( - str(self.app.settings.value("ThroughDelay", 0))) + str(self.app.settings.value("ThroughDelay", 0)) + ) self.app.settings.endArray() @@ -385,8 +399,7 @@ class CalibrationWindow(QtWidgets.QWidget): return delete_num = self.cal_standard_save_selector.currentData() logger.debug("Deleting calibration no %d", delete_num) - num_standards = self.app.settings.beginReadArray( - "CalibrationStandards") + num_standards = self.app.settings.beginReadArray("CalibrationStandards") self.app.settings.endArray() logger.debug("Number of standards known: %d", num_standards) @@ -449,7 +462,8 @@ class CalibrationWindow(QtWidgets.QWidget): self.app.settings.endArray() self.app.settings.beginWriteArray( - "CalibrationStandards", len(names)) + "CalibrationStandards", len(names) + ) for i, name in enumerate(names): self.app.settings.setArrayIndex(i) self.app.settings.setValue("Name", name) @@ -488,8 +502,11 @@ class CalibrationWindow(QtWidgets.QWidget): if len(self.app.worker.rawData11) > 0: # There's raw data, so we can get corrected data logger.debug("Saving and displaying raw data.") - self.app.saveData(self.app.worker.rawData11, - self.app.worker.rawData21, self.app.sweepSource) + self.app.saveData( + self.app.worker.rawData11, + self.app.worker.rawData21, + self.app.sweepSource, + ) self.app.worker.signals.updated.emit() def setOffsetDelay(self, value: float): @@ -498,12 +515,18 @@ class CalibrationWindow(QtWidgets.QWidget): if len(self.app.worker.rawData11) > 0: # There's raw data, so we can get corrected data logger.debug("Applying new offset to existing sweep data.") - self.app.worker.data11, self.app.worker.data21 = \ - self.app.worker.applyCalibration( - self.app.worker.rawData11, self.app.worker.rawData21) + ( + self.app.worker.data11, + self.app.worker.data21, + ) = self.app.worker.applyCalibration( + self.app.worker.rawData11, self.app.worker.rawData21 + ) logger.debug("Saving and displaying corrected data.") - self.app.saveData(self.app.worker.data11, - self.app.worker.data21, self.app.sweepSource) + self.app.saveData( + self.app.worker.data11, + self.app.worker.data21, + self.app.sweepSource, + ) self.app.worker.signals.updated.emit() def calculate(self): @@ -511,7 +534,8 @@ class CalibrationWindow(QtWidgets.QWidget): if self.app.sweep_control.btn_stop.isEnabled(): self.app.showError( "Unable to apply calibration while a sweep is running." - " Please stop the sweep and try again.") + " Please stop the sweep and try again." + ) return cal_element.short_is_ideal = True @@ -528,63 +552,85 @@ class CalibrationWindow(QtWidgets.QWidget): # We are using custom calibration standards - cal_element.short_l0 = getFloatValue( - self.short_l0_input.text()) / 1.0e12 - cal_element.short_l1 = getFloatValue( - self.short_l1_input.text()) / 1.0e24 - cal_element.short_l2 = getFloatValue( - self.short_l2_input.text()) / 1.0e33 - cal_element.short_l3 = getFloatValue( - self.short_l3_input.text()) / 1.0e42 - cal_element.short_length = getFloatValue( - self.short_length.text()) / 1.0e12 + cal_element.short_l0 = ( + getFloatValue(self.short_l0_input.text()) / 1.0e12 + ) + cal_element.short_l1 = ( + getFloatValue(self.short_l1_input.text()) / 1.0e24 + ) + cal_element.short_l2 = ( + getFloatValue(self.short_l2_input.text()) / 1.0e33 + ) + cal_element.short_l3 = ( + getFloatValue(self.short_l3_input.text()) / 1.0e42 + ) + cal_element.short_length = ( + getFloatValue(self.short_length.text()) / 1.0e12 + ) - cal_element.open_c0 = getFloatValue( - self.open_c0_input.text()) / 1.e15 - cal_element.open_c1 = getFloatValue( - self.open_c1_input.text()) / 1.e27 - cal_element.open_c2 = getFloatValue( - self.open_c2_input.text()) / 1.0e36 - cal_element.open_c3 = getFloatValue( - self.open_c3_input.text()) / 1.0e45 - cal_element.openLength = getFloatValue( - self.open_length.text()) / 1.0e12 + cal_element.open_c0 = ( + getFloatValue(self.open_c0_input.text()) / 1.0e15 + ) + cal_element.open_c1 = ( + getFloatValue(self.open_c1_input.text()) / 1.0e27 + ) + cal_element.open_c2 = ( + getFloatValue(self.open_c2_input.text()) / 1.0e36 + ) + cal_element.open_c3 = ( + getFloatValue(self.open_c3_input.text()) / 1.0e45 + ) + cal_element.openLength = ( + getFloatValue(self.open_length.text()) / 1.0e12 + ) - cal_element.load_r = getFloatValue( - self.load_resistance.text()) - cal_element.load_l = getFloatValue( - self.load_inductance.text()) / 1.0e12 - cal_element.load_c = getFloatValue( - self.load_capacitance.text()) / 1.0e15 - cal_element.load_length = getFloatValue( - self.load_length.text()) / 1.0e12 + cal_element.load_r = getFloatValue(self.load_resistance.text()) + cal_element.load_l = ( + getFloatValue(self.load_inductance.text()) / 1.0e12 + ) + cal_element.load_c = ( + getFloatValue(self.load_capacitance.text()) / 1.0e15 + ) + cal_element.load_length = ( + getFloatValue(self.load_length.text()) / 1.0e12 + ) - cal_element.through_length = getFloatValue( - self.through_length.text()) / 1.0e12 + cal_element.through_length = ( + getFloatValue(self.through_length.text()) / 1.0e12 + ) logger.debug("Attempting calibration calculation.") try: self.app.calibration.calc_corrections() self.calibration_status_label.setText( - _format_cal_label(self.app.calibration.size(), - "Application calibration")) + _format_cal_label( + self.app.calibration.size(), "Application calibration" + ) + ) if self.use_ideal_values.isChecked(): self.calibration_source_label.setText( - self.app.calibration.source) + self.app.calibration.source + ) else: self.calibration_source_label.setText( - f"{self.app.calibration.source} (Standards: Custom)") + f"{self.app.calibration.source} (Standards: Custom)" + ) if self.app.worker.rawData11: # There's raw data, so we can get corrected data logger.debug("Applying calibration to existing sweep data.") - self.app.worker.data11, self.app.worker.data21 = ( - self.app.worker.applyCalibration( - self.app.worker.rawData11, - self.app.worker.rawData21)) + ( + self.app.worker.data11, + self.app.worker.data21, + ) = self.app.worker.applyCalibration( + self.app.worker.rawData11, self.app.worker.rawData21 + ) logger.debug("Saving and displaying corrected data.") - self.app.saveData(self.app.worker.data11, - self.app.worker.data21, self.app.sweepSource) + self.app.saveData( + self.app.worker.data11, + self.app.worker.data21, + self.app.sweepSource, + ) self.app.worker.signals.updated.emit() except ValueError as e: if logger.isEnabledFor(logging.DEBUG): @@ -592,23 +638,29 @@ class CalibrationWindow(QtWidgets.QWidget): # showError here hides the calibration window, # so we need to pop up our own QtWidgets.QMessageBox.warning( - self, "Error applying calibration", str(e)) + self, "Error applying calibration", str(e) + ) self.calibration_status_label.setText( - "Applying calibration failed.") + "Applying calibration failed." + ) self.calibration_source_label.setText(self.app.calibration.source) def loadCalibration(self): filename, _ = QtWidgets.QFileDialog.getOpenFileName( - filter="Calibration Files (*.cal);;All files (*.*)") + filter="Calibration Files (*.cal);;All files (*.*)" + ) if filename: self.app.calibration.load(filename) if not self.app.calibration.isValid1Port(): return for i, name in enumerate( - ("short", "open", "load", "through", "isolation", "thrurefl")): + ("short", "open", "load", "through", "isolation", "thrurefl") + ): self.cal_label[name].setText( - _format_cal_label(self.app.calibration.data_size(name), - "Loaded")) + _format_cal_label( + self.app.calibration.data_size(name), "Loaded" + ) + ) if i == 2 and not self.app.calibration.isValid2Port(): break self.calculate() @@ -633,8 +685,9 @@ class CalibrationWindow(QtWidgets.QWidget): if not filename: logger.debug("No file name selected.") return - self.app.calibration.notes = self.notes_textedit.toPlainText( - ).splitlines() + self.app.calibration.notes = ( + self.notes_textedit.toPlainText().splitlines() + ) try: self.app.calibration.save(filename) self.app.settings.setValue("CalibrationFile", filename) @@ -648,7 +701,8 @@ class CalibrationWindow(QtWidgets.QWidget): self.cal_load_box.setDisabled(self.use_ideal_values.isChecked()) self.cal_through_box.setDisabled(self.use_ideal_values.isChecked()) self.cal_standard_save_box.setDisabled( - self.use_ideal_values.isChecked()) + self.use_ideal_values.isChecked() + ) def automaticCalibration(self): self.btn_automatic.setDisabled(True) @@ -662,14 +716,15 @@ class CalibrationWindow(QtWidgets.QWidget): "Before starting, ensure you have Open, Short and Load" " standards available, and the cables you wish to have" " calibrated with the device connected.

" - "If you want a 2-port calibration, also have a \"through\"" + 'If you want a 2-port calibration, also have a "through"' " connector to hand.

" "The best results are achieved by having the NanoVNA" " calibrated on-device for the full span of interest and saved" " to save slot 0 before starting.

" "Once you are ready to proceed, press Ok." ), - QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) + QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, + ) response = introduction.exec() if response != QtWidgets.QMessageBox.Ok: self.btn_automatic.setDisabled(False) @@ -679,8 +734,10 @@ class CalibrationWindow(QtWidgets.QWidget): QtWidgets.QMessageBox( QtWidgets.QMessageBox.Information, "NanoVNA not connected", - ("Please ensure the NanoVNA is connected before attempting" - " calibration.") + ( + "Please ensure the NanoVNA is connected before attempting" + " calibration." + ), ).exec() self.btn_automatic.setDisabled(False) return @@ -689,8 +746,10 @@ class CalibrationWindow(QtWidgets.QWidget): QtWidgets.QMessageBox( QtWidgets.QMessageBox.Information, "Continuous sweep enabled", - ("Please disable continuous sweeping before attempting" - " calibration.") + ( + "Please disable continuous sweeping before attempting" + " calibration." + ), ).exec() self.btn_automatic.setDisabled(False) return @@ -699,11 +758,12 @@ class CalibrationWindow(QtWidgets.QWidget): QtWidgets.QMessageBox.Information, "Calibrate short", ( - "Please connect the \"short\" standard to port 0 of the" + 'Please connect the "short" standard to port 0 of the' " NanoVNA.\n\n" "Press Ok when you are ready to continue." ), - QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) + QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, + ) response = short_step.exec() if response != QtWidgets.QMessageBox.Ok: @@ -719,7 +779,8 @@ class CalibrationWindow(QtWidgets.QWidget): def automaticCalibrationStep(self): if self.nextStep == -1: self.app.worker.signals.finished.disconnect( - self.automaticCalibrationStep) + self.automaticCalibrationStep + ) return if self.nextStep == 0: @@ -731,20 +792,22 @@ class CalibrationWindow(QtWidgets.QWidget): QtWidgets.QMessageBox.Information, "Calibrate open", ( - "Please connect the \"open\" standard to port 0 of the" + 'Please connect the "open" standard to port 0 of the' " NanoVNA.\n\n" "Either use a supplied open, or leave the end of the" " cable unconnected if desired.\n\n" "Press Ok when you are ready to continue." ), - QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) + QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, + ) response = open_step.exec() if response != QtWidgets.QMessageBox.Ok: self.nextStep = -1 self.btn_automatic.setDisabled(False) self.app.worker.signals.finished.disconnect( - self.automaticCalibrationStep) + self.automaticCalibrationStep + ) return self.app.sweep_start() return @@ -757,18 +820,20 @@ class CalibrationWindow(QtWidgets.QWidget): QtWidgets.QMessageBox.Information, "Calibrate load", ( - "Please connect the \"load\" standard to port 0 of the" + 'Please connect the "load" standard to port 0 of the' " NanoVNA.\n\n" "Press Ok when you are ready to continue." ), - QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) + QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, + ) response = load_step.exec() if response != QtWidgets.QMessageBox.Ok: self.btn_automatic.setDisabled(False) self.nextStep = -1 self.app.worker.signals.finished.disconnect( - self.automaticCalibrationStep) + self.automaticCalibrationStep + ) return self.app.sweep_start() return @@ -784,45 +849,51 @@ class CalibrationWindow(QtWidgets.QWidget): "The required steps for a 1-port calibration are now" " complete.\n\n" "If you wish to continue and perform a 2-port calibration," - " press \"Yes\". To apply the 1-port calibration and stop," - " press \"Apply\"" + ' press "Yes". To apply the 1-port calibration and stop,' + ' press "Apply"' ), - QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Apply | - QtWidgets.QMessageBox.Cancel) + QtWidgets.QMessageBox.Yes + | QtWidgets.QMessageBox.Apply + | QtWidgets.QMessageBox.Cancel, + ) response = continue_step.exec() if response == QtWidgets.QMessageBox.Apply: self.calculate() self.nextStep = -1 self.app.worker.signals.finished.disconnect( - self.automaticCalibrationStep) + self.automaticCalibrationStep + ) self.btn_automatic.setDisabled(False) return if response != QtWidgets.QMessageBox.Yes: self.btn_automatic.setDisabled(False) self.nextStep = -1 self.app.worker.signals.finished.disconnect( - self.automaticCalibrationStep) + self.automaticCalibrationStep + ) return isolation_step = QtWidgets.QMessageBox( QtWidgets.QMessageBox.Information, "Calibrate isolation", ( - "Please connect the \"load\" standard to port 1 of the" + 'Please connect the "load" standard to port 1 of the' " NanoVNA.\n\n" "If available, also connect a load standard to" " port 0.\n\n" "Press Ok when you are ready to continue." ), - QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) + QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, + ) response = isolation_step.exec() if response != QtWidgets.QMessageBox.Ok: self.btn_automatic.setDisabled(False) self.nextStep = -1 self.app.worker.signals.finished.disconnect( - self.automaticCalibrationStep) + self.automaticCalibrationStep + ) return self.app.sweep_start() return @@ -835,18 +906,20 @@ class CalibrationWindow(QtWidgets.QWidget): QtWidgets.QMessageBox.Information, "Calibrate through", ( - "Please connect the \"through\" standard between" + 'Please connect the "through" standard between' " port 0 and port 1 of the NanoVNA.\n\n" "Press Ok when you are ready to continue." ), - QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) + QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel, + ) response = through_step.exec() if response != QtWidgets.QMessageBox.Ok: self.btn_automatic.setDisabled(False) self.nextStep = -1 self.app.worker.signals.finished.disconnect( - self.automaticCalibrationStep) + self.automaticCalibrationStep + ) return self.app.sweep_start() return @@ -860,21 +933,24 @@ class CalibrationWindow(QtWidgets.QWidget): "Calibrate complete", ( "The calibration process is now complete. Press" - " \"Apply\" to apply the calibration parameters." + ' "Apply" to apply the calibration parameters.' ), - QtWidgets.QMessageBox.Apply | QtWidgets.QMessageBox.Cancel) + QtWidgets.QMessageBox.Apply | QtWidgets.QMessageBox.Cancel, + ) response = apply_step.exec() if response != QtWidgets.QMessageBox.Apply: self.btn_automatic.setDisabled(False) self.nextStep = -1 self.app.worker.signals.finished.disconnect( - self.automaticCalibrationStep) + self.automaticCalibrationStep + ) return self.calculate() self.btn_automatic.setDisabled(False) self.nextStep = -1 self.app.worker.signals.finished.disconnect( - self.automaticCalibrationStep) + self.automaticCalibrationStep + ) return diff --git a/src/NanoVNASaver/Windows/Defaults.py b/src/NanoVNASaver/Windows/Defaults.py index 9528a96..2862f00 100644 --- a/src/NanoVNASaver/Windows/Defaults.py +++ b/src/NanoVNASaver/Windows/Defaults.py @@ -23,7 +23,9 @@ from PyQt5 import QtWidgets logger = logging.getLogger(__name__) -def make_scrollable(window: QtWidgets.QWidget, layout: QtWidgets.QLayout) -> None: +def make_scrollable( + window: QtWidgets.QWidget, layout: QtWidgets.QLayout +) -> None: area = QtWidgets.QScrollArea() area.setWidgetResizable(True) outer = QtWidgets.QVBoxLayout() diff --git a/src/NanoVNASaver/Windows/DeviceSettings.py b/src/NanoVNASaver/Windows/DeviceSettings.py index cd20df6..4b0b126 100644 --- a/src/NanoVNASaver/Windows/DeviceSettings.py +++ b/src/NanoVNASaver/Windows/DeviceSettings.py @@ -65,9 +65,11 @@ class DeviceSettingsWindow(QtWidgets.QWidget): settings_layout = QtWidgets.QFormLayout(settings_box) self.chkValidateInputData = QtWidgets.QCheckBox( - "Validate received data") + "Validate received data" + ) validate_input = self.app.settings.value( - "SerialInputValidation", False, bool) + "SerialInputValidation", False, bool + ) self.chkValidateInputData.setChecked(validate_input) self.chkValidateInputData.stateChanged.connect(self.updateValidation) settings_layout.addRow("Validation", self.chkValidateInputData) @@ -100,12 +102,10 @@ class DeviceSettingsWindow(QtWidgets.QWidget): settings_layout.addRow(form_layout) def _set_datapoint_index(self, dpoints: int): - self.datapoints.setCurrentIndex( - self.datapoints.findText(str(dpoints))) + self.datapoints.setCurrentIndex(self.datapoints.findText(str(dpoints))) def _set_bandwidth_index(self, bw: int): - self.bandwidth.setCurrentIndex( - self.bandwidth.findText(str(bw))) + self.bandwidth.setCurrentIndex(self.bandwidth.findText(str(bw))) def show(self): super().show() @@ -120,10 +120,10 @@ class DeviceSettingsWindow(QtWidgets.QWidget): self.btnCaptureScreenshot.setDisabled(True) return - self.label["status"].setText( - f"Connected to {self.app.vna.name}.") + self.label["status"].setText(f"Connected to {self.app.vna.name}.") self.label["firmware"].setText( - f"{self.app.vna.name} v{self.app.vna.version}") + f"{self.app.vna.name} v{self.app.vna.version}" + ) if self.app.worker.running: self.label["calibration"].setText("(Sweep running)") else: diff --git a/src/NanoVNASaver/Windows/DisplaySettings.py b/src/NanoVNASaver/Windows/DisplaySettings.py index 36c1daf..fdfd1c9 100644 --- a/src/NanoVNASaver/Windows/DisplaySettings.py +++ b/src/NanoVNASaver/Windows/DisplaySettings.py @@ -22,8 +22,7 @@ from typing import List from PyQt5 import QtWidgets, QtCore, QtGui from NanoVNASaver import Defaults -from NanoVNASaver.Charts.Chart import ( - Chart, ChartColors) +from NanoVNASaver.Charts.Chart import Chart, ChartColors from NanoVNASaver.Windows.Bands import BandsWindow from NanoVNASaver.Windows.Defaults import make_scrollable from NanoVNASaver.Windows.MarkerSettings import MarkerSettingsWindow @@ -60,20 +59,24 @@ class DisplaySettingsWindow(QtWidgets.QWidget): self.returnloss_group.addButton(self.returnloss_is_negative) display_options_layout.addRow( - "Return loss is:", self.returnloss_is_negative) + "Return loss is:", self.returnloss_is_negative + ) display_options_layout.addRow("", self.returnloss_is_positive) self.returnloss_is_positive.setChecked( - Defaults.cfg.chart.returnloss_is_positive) + Defaults.cfg.chart.returnloss_is_positive + ) self.returnloss_is_negative.setChecked( - not Defaults.cfg.chart.returnloss_is_positive) + not Defaults.cfg.chart.returnloss_is_positive + ) self.returnloss_is_positive.toggled.connect(self.changeReturnLoss) self.changeReturnLoss() self.show_lines_option = QtWidgets.QCheckBox("Show lines") show_lines_label = QtWidgets.QLabel( - "Displays a thin line between data points") + "Displays a thin line between data points" + ) self.show_lines_option.stateChanged.connect(self.changeShowLines) display_options_layout.addRow(self.show_lines_option, show_lines_label) @@ -106,8 +109,7 @@ class DisplaySettingsWindow(QtWidgets.QWidget): self.lineThicknessInput.setSuffix(" px") self.lineThicknessInput.setAlignment(QtCore.Qt.AlignRight) self.lineThicknessInput.valueChanged.connect(self.changeLineThickness) - display_options_layout.addRow( - "Line thickness", self.lineThicknessInput) + display_options_layout.addRow("Line thickness", self.lineThicknessInput) self.markerSizeInput = QtWidgets.QSpinBox() self.markerSizeInput.setMinimumHeight(20) @@ -122,25 +124,31 @@ class DisplaySettingsWindow(QtWidgets.QWidget): display_options_layout.addRow("Marker size", self.markerSizeInput) self.show_marker_number_option = QtWidgets.QCheckBox( - "Show marker numbers") + "Show marker numbers" + ) show_marker_number_label = QtWidgets.QLabel( - "Displays the marker number next to the marker") + "Displays the marker number next to the marker" + ) self.show_marker_number_option.stateChanged.connect( - self.changeShowMarkerNumber) + self.changeShowMarkerNumber + ) display_options_layout.addRow( - self.show_marker_number_option, show_marker_number_label) + self.show_marker_number_option, show_marker_number_label + ) self.filled_marker_option = QtWidgets.QCheckBox("Filled markers") filled_marker_label = QtWidgets.QLabel( - "Shows the marker as a filled triangle") - self.filled_marker_option.stateChanged.connect( - self.changeFilledMarkers) + "Shows the marker as a filled triangle" + ) + self.filled_marker_option.stateChanged.connect(self.changeFilledMarkers) display_options_layout.addRow( - self.filled_marker_option, filled_marker_label) + self.filled_marker_option, filled_marker_label + ) self.marker_tip_group = QtWidgets.QButtonGroup() self.marker_at_center = QtWidgets.QRadioButton( - "At the center of the marker") + "At the center of the marker" + ) self.marker_at_tip = QtWidgets.QRadioButton("At the tip of the marker") self.marker_tip_group.addButton(self.marker_at_center) self.marker_tip_group.addButton(self.marker_at_tip) @@ -183,11 +191,12 @@ class DisplaySettingsWindow(QtWidgets.QWidget): self.show_bands = QtWidgets.QCheckBox("Show bands") self.show_bands.setChecked(self.app.bands.enabled) self.show_bands.stateChanged.connect( - lambda: self.setShowBands(self.show_bands.isChecked())) + lambda: self.setShowBands(self.show_bands.isChecked()) + ) bands_layout.addRow(self.show_bands) bands_layout.addRow( - "Chart bands", - self.color_picker("BandsColor", "bands")) + "Chart bands", self.color_picker("BandsColor", "bands") + ) self.btn_manage_bands = QtWidgets.QPushButton("Manage bands") self.btn_manage_bands.setMinimumHeight(20) @@ -201,16 +210,19 @@ class DisplaySettingsWindow(QtWidgets.QWidget): vswr_marker_layout = QtWidgets.QFormLayout(vswr_marker_box) self.vswrMarkers: List[float] = self.app.settings.value( - "VSWRMarkers", [], float) + "VSWRMarkers", [], float + ) if isinstance(self.vswrMarkers, float): # Single values from the .ini become floats rather than lists. # Convert them. - self.vswrMarkers = ([] if self.vswrMarkers == 0.0 else - [self.vswrMarkers]) + self.vswrMarkers = ( + [] if self.vswrMarkers == 0.0 else [self.vswrMarkers] + ) vswr_marker_layout.addRow( - "VSWR Markers", self.color_picker("VSWRColor", "swr")) + "VSWR Markers", self.color_picker("VSWRColor", "swr") + ) self.vswr_marker_dropdown = QtWidgets.QComboBox() self.vswr_marker_dropdown.setMinimumHeight(20) @@ -281,7 +293,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget): else: chart00_selection.setCurrentText("S11 Smith Chart") chart00_selection.currentTextChanged.connect( - lambda: self.changeChart(0, 0, chart00_selection.currentText())) + lambda: self.changeChart(0, 0, chart00_selection.currentText()) + ) charts_layout.addWidget(chart00_selection, 0, 0) chart01_selection = QtWidgets.QComboBox() @@ -293,7 +306,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget): else: chart01_selection.setCurrentText("S11 Return Loss") chart01_selection.currentTextChanged.connect( - lambda: self.changeChart(0, 1, chart01_selection.currentText())) + lambda: self.changeChart(0, 1, chart01_selection.currentText()) + ) charts_layout.addWidget(chart01_selection, 0, 1) chart02_selection = QtWidgets.QComboBox() @@ -305,7 +319,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget): else: chart02_selection.setCurrentText("None") chart02_selection.currentTextChanged.connect( - lambda: self.changeChart(0, 2, chart02_selection.currentText())) + lambda: self.changeChart(0, 2, chart02_selection.currentText()) + ) charts_layout.addWidget(chart02_selection, 0, 2) chart10_selection = QtWidgets.QComboBox() @@ -317,7 +332,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget): else: chart10_selection.setCurrentText("S21 Polar Plot") chart10_selection.currentTextChanged.connect( - lambda: self.changeChart(1, 0, chart10_selection.currentText())) + lambda: self.changeChart(1, 0, chart10_selection.currentText()) + ) charts_layout.addWidget(chart10_selection, 1, 0) chart11_selection = QtWidgets.QComboBox() @@ -329,7 +345,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget): else: chart11_selection.setCurrentText("S21 Gain") chart11_selection.currentTextChanged.connect( - lambda: self.changeChart(1, 1, chart11_selection.currentText())) + lambda: self.changeChart(1, 1, chart11_selection.currentText()) + ) charts_layout.addWidget(chart11_selection, 1, 1) chart12_selection = QtWidgets.QComboBox() @@ -341,7 +358,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget): else: chart12_selection.setCurrentText("None") chart12_selection.currentTextChanged.connect( - lambda: self.changeChart(1, 2, chart12_selection.currentText())) + lambda: self.changeChart(1, 2, chart12_selection.currentText()) + ) charts_layout.addWidget(chart12_selection, 1, 2) self.changeChart(0, 0, chart00_selection.currentText()) @@ -353,30 +371,36 @@ class DisplaySettingsWindow(QtWidgets.QWidget): chart_colors = ChartColors() Chart.color.background = self.app.settings.value( - "BackgroundColor", defaultValue=chart_colors.background, - type=QtGui.QColor) + "BackgroundColor", + defaultValue=chart_colors.background, + type=QtGui.QColor, + ) Chart.color.foreground = self.app.settings.value( - "ForegroundColor", defaultValue=chart_colors.foreground, - type=QtGui.QColor) + "ForegroundColor", + defaultValue=chart_colors.foreground, + type=QtGui.QColor, + ) Chart.color.text = self.app.settings.value( - "TextColor", defaultValue=chart_colors.text, - type=QtGui.QColor) + "TextColor", defaultValue=chart_colors.text, type=QtGui.QColor + ) self.bandsColor = self.app.settings.value( - "BandsColor", defaultValue=chart_colors.bands, - type=QtGui.QColor) + "BandsColor", defaultValue=chart_colors.bands, type=QtGui.QColor + ) self.app.bands.color = Chart.color.bands Chart.color.swr = self.app.settings.value( - "VSWRColor", defaultValue=chart_colors.swr, - type=QtGui.QColor) + "VSWRColor", defaultValue=chart_colors.swr, type=QtGui.QColor + ) self.dark_mode_option.setChecked(Defaults.cfg.gui.dark_mode) self.show_lines_option.setChecked(Defaults.cfg.chart.show_lines) self.show_marker_number_option.setChecked( - Defaults.cfg.chart.marker_label) + Defaults.cfg.chart.marker_label + ) self.filled_marker_option.setChecked(Defaults.cfg.chart.marker_filled) - if self.app.settings.value("UseCustomColors", - defaultValue=False, type=bool): + if self.app.settings.value( + "UseCustomColors", defaultValue=False, type=bool + ): self.dark_mode_option.setDisabled(True) self.dark_mode_option.setChecked(False) self.use_custom_colors.setChecked(True) @@ -395,20 +419,23 @@ class DisplaySettingsWindow(QtWidgets.QWidget): def trace_colors(self, layout: QtWidgets.QLayout): for setting, name, attr in ( - ('SweepColor', 'Sweep color', 'sweep'), - ('SecondarySweepColor', 'Second sweep color', 'sweep_secondary'), - ('ReferenceColor', 'Reference color', 'reference'), - ('SecondaryReferenceColor', - 'Second reference color', 'reference_secondary'), + ("SweepColor", "Sweep color", "sweep"), + ("SecondarySweepColor", "Second sweep color", "sweep_secondary"), + ("ReferenceColor", "Reference color", "reference"), + ( + "SecondaryReferenceColor", + "Second reference color", + "reference_secondary", + ), ): cp = self.color_picker(setting, attr) layout.addRow(name, cp) def custom_colors(self, layout: QtWidgets.QLayout): for setting, name, attr in ( - ('BackgroundColor', 'Chart background', 'background'), - ('ForegroundColor', 'Chart foreground', 'foreground'), - ('TextColor', 'Chart text', 'text'), + ("BackgroundColor", "Chart background", "background"), + ("ForegroundColor", "Chart foreground", "foreground"), + ("TextColor", "Chart text", "text"), ): cp = self.color_picker(setting, attr) layout.addRow(name, cp) @@ -419,7 +446,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget): cp.setMinimumHeight(20) default = getattr(Chart.color, attr) color = self.app.settings.value( - setting, defaultValue=default, type=QtGui.QColor) + setting, defaultValue=default, type=QtGui.QColor + ) setattr(Chart.color, attr, color) self.callback_params[cp] = (setting, attr) cp.clicked.connect(self.setColor) @@ -466,17 +494,18 @@ class DisplaySettingsWindow(QtWidgets.QWidget): def changeShowMarkerNumber(self): Defaults.cfg.chart.marker_label = bool( - self.show_marker_number_option.isChecked()) + self.show_marker_number_option.isChecked() + ) self.updateCharts() def changeFilledMarkers(self): Defaults.cfg.chart.marker_filled = bool( - self.filled_marker_option.isChecked()) + self.filled_marker_option.isChecked() + ) self.updateCharts() def changeMarkerAtTip(self): - Defaults.cfg.chart.marker_at_tip = bool( - self.marker_at_tip.isChecked()) + Defaults.cfg.chart.marker_at_tip = bool(self.marker_at_tip.isChecked()) self.updateCharts() def changePointSize(self, size: int): @@ -521,7 +550,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget): color = getattr(Chart.color, attr) color = QtWidgets.QColorDialog.getColor( - color, options=QtWidgets.QColorDialog.ShowAlphaChannel) + color, options=QtWidgets.QColorDialog.ShowAlphaChannel + ) if not color.isValid(): logger.info("Invalid color") @@ -566,7 +596,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget): new_marker.updated.connect(self.app.markerUpdated) label, layout = new_marker.getRow() self.app.marker_control.layout.insertRow( - Marker.count() - 1, label, layout) + Marker.count() - 1, label, layout + ) self.btn_remove_marker.setDisabled(False) if Marker.count() >= 2: @@ -594,8 +625,12 @@ class DisplaySettingsWindow(QtWidgets.QWidget): def addVSWRMarker(self): value, selected = QtWidgets.QInputDialog.getDouble( - self, "Add VSWR Marker", "VSWR value to show:", - min=1.001, decimals=3) + self, + "Add VSWR Marker", + "VSWR value to show:", + min=1.001, + decimals=3, + ) if selected: self.vswrMarkers.append(value) if self.vswr_marker_dropdown.itemText(0) == "None": @@ -612,7 +647,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget): value = float(value_str) self.vswrMarkers.remove(value) self.vswr_marker_dropdown.removeItem( - self.vswr_marker_dropdown.currentIndex()) + self.vswr_marker_dropdown.currentIndex() + ) if self.vswr_marker_dropdown.count() == 0: self.vswr_marker_dropdown.addItem("None") self.app.settings.remove("VSWRMarkers") diff --git a/src/NanoVNASaver/Windows/Files.py b/src/NanoVNASaver/Windows/Files.py index 8586796..9a0627b 100644 --- a/src/NanoVNASaver/Windows/Files.py +++ b/src/NanoVNASaver/Windows/Files.py @@ -68,27 +68,32 @@ class FilesWindow(QtWidgets.QWidget): btn_open_file_window = QtWidgets.QPushButton("Files ...") btn_open_file_window.clicked.connect( - lambda: self.app.display_window("file")) + lambda: self.app.display_window("file") + ) def exportFile(self, nr_params: int = 1): if len(self.app.data.s11) == 0: QtWidgets.QMessageBox.warning( - self, "No data to save", "There is no data to save.") + self, "No data to save", "There is no data to save." + ) return if nr_params > 2 and len(self.app.data.s21) == 0: QtWidgets.QMessageBox.warning( - self, "No S21 data to save", "There is no S21 data to save.") + self, "No S21 data to save", "There is no S21 data to save." + ) return filedialog = QtWidgets.QFileDialog(self) if nr_params == 1: filedialog.setDefaultSuffix("s1p") filedialog.setNameFilter( - "Touchstone 1-Port Files (*.s1p);;All files (*.*)") + "Touchstone 1-Port Files (*.s1p);;All files (*.*)" + ) else: filedialog.setDefaultSuffix("s2p") filedialog.setNameFilter( - "Touchstone 2-Port Files (*.s2p);;All files (*.*)") + "Touchstone 2-Port Files (*.s2p);;All files (*.*)" + ) filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) selected = filedialog.exec() if not selected: @@ -113,7 +118,8 @@ class FilesWindow(QtWidgets.QWidget): def loadReferenceFile(self): filename, _ = QtWidgets.QFileDialog.getOpenFileName( - filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") + filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)" + ) if filename != "": self.app.resetReference() t = Touchstone(filename) @@ -122,7 +128,8 @@ class FilesWindow(QtWidgets.QWidget): def loadSweepFile(self): filename, _ = QtWidgets.QFileDialog.getOpenFileName( - filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") + filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)" + ) if filename != "": self.app.data.s11 = [] self.app.data.s21 = [] diff --git a/src/NanoVNASaver/Windows/MarkerSettings.py b/src/NanoVNASaver/Windows/MarkerSettings.py index 2912a7f..ff6bcd4 100644 --- a/src/NanoVNASaver/Windows/MarkerSettings.py +++ b/src/NanoVNASaver/Windows/MarkerSettings.py @@ -28,12 +28,16 @@ logger = logging.getLogger(__name__) class MarkerSettingsWindow(QtWidgets.QWidget): - exampleData11 = [Datapoint(123000000, 0.89, -0.11), - Datapoint(123500000, 0.9, -0.1), - Datapoint(124000000, 0.91, -0.95)] - exampleData21 = [Datapoint(123000000, -0.25, 0.49), - Datapoint(123456000, -0.3, 0.5), - Datapoint(124000000, -0.2, 0.5)] + exampleData11 = [ + Datapoint(123000000, 0.89, -0.11), + Datapoint(123500000, 0.9, -0.1), + Datapoint(124000000, 0.91, -0.95), + ] + exampleData21 = [ + Datapoint(123000000, -0.25, 0.49), + Datapoint(123456000, -0.3, 0.5), + Datapoint(124000000, -0.2, 0.5), + ] def __init__(self, app: QtWidgets.QWidget): super().__init__() @@ -50,10 +54,10 @@ class MarkerSettingsWindow(QtWidgets.QWidget): settings_group_box = QtWidgets.QGroupBox("Settings") settings_group_box_layout = QtWidgets.QFormLayout(settings_group_box) - self.checkboxColouredMarker = QtWidgets.QCheckBox( - "Colored marker name") + self.checkboxColouredMarker = QtWidgets.QCheckBox("Colored marker name") self.checkboxColouredMarker.setChecked( - self.app.settings.value("ColoredMarkerNames", True, bool)) + self.app.settings.value("ColoredMarkerNames", True, bool) + ) self.checkboxColouredMarker.stateChanged.connect(self.updateMarker) settings_group_box_layout.addRow(self.checkboxColouredMarker) @@ -103,7 +107,8 @@ class MarkerSettingsWindow(QtWidgets.QWidget): def updateMarker(self): self.exampleMarker.setFrequency(123456000) self.exampleMarker.setColoredText( - self.checkboxColouredMarker.isChecked()) + self.checkboxColouredMarker.isChecked() + ) self.exampleMarker.setFieldSelection(self.currentFieldSelection) self.exampleMarker.findLocation(self.exampleData11) self.exampleMarker.resetLabels() @@ -125,8 +130,11 @@ class MarkerSettingsWindow(QtWidgets.QWidget): self.savedFieldSelection = self.currentFieldSelection[:] self.app.settings.setValue("MarkerFields", self.savedFieldSelection) self.app.settings.setValue( - "ColoredMarkerNames", self.checkboxColouredMarker.isChecked()) - for m in self.app.markers + [self.app.delta_marker, ]: + "ColoredMarkerNames", self.checkboxColouredMarker.isChecked() + ) + for m in self.app.markers + [ + self.app.delta_marker, + ]: m.setFieldSelection(self.savedFieldSelection) m.setColoredText(self.checkboxColouredMarker.isChecked()) diff --git a/src/NanoVNASaver/Windows/Screenshot.py b/src/NanoVNASaver/Windows/Screenshot.py index c967250..1ae20a2 100644 --- a/src/NanoVNASaver/Windows/Screenshot.py +++ b/src/NanoVNASaver/Windows/Screenshot.py @@ -61,25 +61,34 @@ class ScreenshotWindow(QtWidgets.QLabel): self.pix.scaled( self.size(), QtCore.Qt.KeepAspectRatio, - QtCore.Qt.FastTransformation)) + QtCore.Qt.FastTransformation, + ) + ) w, h = pixmap.width(), pixmap.height() self.action_original_size.setText( - "Original size (" + str(w) + "x" + str(h) + ")") + "Original size (" + str(w) + "x" + str(h) + ")" + ) self.action_2x_size.setText( - "2x size (" + str(w * 2) + "x" + str(h * 2) + ")") + "2x size (" + str(w * 2) + "x" + str(h * 2) + ")" + ) self.action_3x_size.setText( - "3x size (" + str(w * 3) + "x" + str(h * 3) + ")") + "3x size (" + str(w * 3) + "x" + str(h * 3) + ")" + ) self.action_4x_size.setText( - "4x size (" + str(w * 4) + "x" + str(h * 4) + ")") + "4x size (" + str(w * 4) + "x" + str(h * 4) + ")" + ) self.action_5x_size.setText( - "5x size (" + str(w * 5) + "x" + str(h * 5) + ")") + "5x size (" + str(w * 5) + "x" + str(h * 5) + ")" + ) def saveScreenshot(self): if self.pix is not None: logger.info("Saving screenshot to file...") filename, _ = QtWidgets.QFileDialog.getSaveFileName( - parent=self, caption="Save image", - filter="PNG (*.png);;All files (*.*)") + parent=self, + caption="Save image", + filter="PNG (*.png);;All files (*.*)", + ) logger.debug("Filename: %s", filename) if filename != "": @@ -94,9 +103,13 @@ class ScreenshotWindow(QtWidgets.QLabel): self.pix.scaled( self.size(), QtCore.Qt.KeepAspectRatio, - QtCore.Qt.FastTransformation)) + QtCore.Qt.FastTransformation, + ) + ) def setScale(self, scale): - width, height = (self.pix.size().width() * scale, - self.pix.size().height() * scale) + width, height = ( + self.pix.size().width() * scale, + self.pix.size().height() * scale, + ) self.resize(width, height) diff --git a/src/NanoVNASaver/Windows/SweepSettings.py b/src/NanoVNASaver/Windows/SweepSettings.py index 699a408..32a611e 100644 --- a/src/NanoVNASaver/Windows/SweepSettings.py +++ b/src/NanoVNASaver/Windows/SweepSettings.py @@ -21,7 +21,8 @@ from functools import partial from PyQt5 import QtWidgets, QtCore from NanoVNASaver.Formatting import ( - format_frequency_short, format_frequency_sweep, + format_frequency_short, + format_frequency_sweep, ) from NanoVNASaver.Settings.Sweep import SweepMode from NanoVNASaver.Windows.Defaults import make_scrollable @@ -59,11 +60,12 @@ class SweepSettingsWindow(QtWidgets.QWidget): input_title = QtWidgets.QLineEdit(self.app.sweep.properties.name) input_title.setMinimumHeight(20) input_title.editingFinished.connect( - lambda: self.update_title(input_title.text())) + lambda: self.update_title(input_title.text()) + ) layout.addRow(input_title) return box - def settings_box(self) -> 'QtWidgets.QWidget': + def settings_box(self) -> "QtWidgets.QWidget": box = QtWidgets.QGroupBox("Settings") layout = QtWidgets.QFormLayout(box) @@ -73,25 +75,29 @@ class SweepSettingsWindow(QtWidgets.QWidget): radio_button = QtWidgets.QRadioButton("Single sweep") radio_button.setMinimumHeight(20) radio_button.setChecked( - self.app.sweep.properties.mode == SweepMode.SINGLE) - radio_button.clicked.connect( - lambda: self.update_mode(SweepMode.SINGLE)) + self.app.sweep.properties.mode == SweepMode.SINGLE + ) + radio_button.clicked.connect(lambda: self.update_mode(SweepMode.SINGLE)) sweep_btn_layout.addWidget(radio_button) radio_button = QtWidgets.QRadioButton("Continous sweep") radio_button.setMinimumHeight(20) radio_button.setChecked( - self.app.sweep.properties.mode == SweepMode.CONTINOUS) + self.app.sweep.properties.mode == SweepMode.CONTINOUS + ) radio_button.clicked.connect( - lambda: self.update_mode(SweepMode.CONTINOUS)) + lambda: self.update_mode(SweepMode.CONTINOUS) + ) sweep_btn_layout.addWidget(radio_button) radio_button = QtWidgets.QRadioButton("Averaged sweep") radio_button.setMinimumHeight(20) radio_button.setChecked( - self.app.sweep.properties.mode == SweepMode.AVERAGE) + self.app.sweep.properties.mode == SweepMode.AVERAGE + ) radio_button.clicked.connect( - lambda: self.update_mode(SweepMode.AVERAGE)) + lambda: self.update_mode(SweepMode.AVERAGE) + ) sweep_btn_layout.addWidget(radio_button) layout.addRow(sweep_btn_layout) @@ -101,7 +107,8 @@ class SweepSettingsWindow(QtWidgets.QWidget): "Logarithmic sweeping changes the step width in each segment" " in logarithmical manner. Useful in conjunction with small" " amount of datapoints and many segments. Step display in" - " SweepControl cannot reflect this currently.") + " SweepControl cannot reflect this currently." + ) label.setWordWrap(True) label.setMinimumSize(600, 70) layout.addRow(label) @@ -109,26 +116,32 @@ class SweepSettingsWindow(QtWidgets.QWidget): checkbox.setMinimumHeight(20) checkbox.setCheckState(self.app.sweep.properties.logarithmic) checkbox.toggled.connect( - lambda: self.update_logarithmic(checkbox.isChecked())) + lambda: self.update_logarithmic(checkbox.isChecked()) + ) layout.addRow(checkbox) # Averaging label = QtWidgets.QLabel( "Averaging allows discarding outlying samples to get better" - " averages. Common values are 3/0, 5/2, 9/4 and 25/6.") + " averages. Common values are 3/0, 5/2, 9/4 and 25/6." + ) label.setWordWrap(True) label.setMinimumHeight(50) layout.addRow(label) averages = QtWidgets.QLineEdit( - str(self.app.sweep.properties.averages[0])) + str(self.app.sweep.properties.averages[0]) + ) averages.setMinimumHeight(20) truncates = QtWidgets.QLineEdit( - str(self.app.sweep.properties.averages[1])) + str(self.app.sweep.properties.averages[1]) + ) truncates.setMinimumHeight(20) averages.editingFinished.connect( - lambda: self.update_averaging(averages, truncates)) + lambda: self.update_averaging(averages, truncates) + ) truncates.editingFinished.connect( - lambda: self.update_averaging(averages, truncates)) + lambda: self.update_averaging(averages, truncates) + ) layout.addRow("Number of measurements to average", averages) layout.addRow("Number to discard", truncates) @@ -136,7 +149,8 @@ class SweepSettingsWindow(QtWidgets.QWidget): label = QtWidgets.QLabel( "Some times when you measure amplifiers you need to use an" " attenuator in line with the S21 input (CH1) here you can" - " specify it.") + " specify it." + ) label.setWordWrap(True) label.setMinimumHeight(50) layout.addRow(label) @@ -144,11 +158,12 @@ class SweepSettingsWindow(QtWidgets.QWidget): input_att = QtWidgets.QLineEdit(str(self.app.s21att)) input_att.setMinimumHeight(20) input_att.editingFinished.connect( - lambda: self.update_attenuator(input_att)) + lambda: self.update_attenuator(input_att) + ) layout.addRow("Attenuator in port CH1 (s21) in dB", input_att) return box - def sweep_box(self) -> 'QtWidgets.QWidget': + def sweep_box(self) -> "QtWidgets.QWidget": box = QtWidgets.QGroupBox("Sweep band") layout = QtWidgets.QFormLayout(box) sweep_pad_layout = QtWidgets.QHBoxLayout() @@ -162,7 +177,11 @@ class SweepSettingsWindow(QtWidgets.QWidget): sweep_pad_layout.addWidget(QtWidgets.QLabel("Pad band limits:")) for btn_label, value in ( - ("None", 0), ("10%", 10), ("25%", 25), ("100%", 100),): + ("None", 0), + ("10%", 10), + ("25%", 25), + ("100%", 100), + ): radio_button = QtWidgets.QRadioButton(btn_label) radio_button.setMinimumHeight(20) radio_button.setChecked(self.padding == value) @@ -186,20 +205,33 @@ class SweepSettingsWindow(QtWidgets.QWidget): power_sel = QtWidgets.QComboBox() power_sel.addItems(power_descs) power_sel.currentTextChanged.connect( - partial(self.update_tx_power, freq_range)) - self._power_layout.addRow("TX power {}..{}".format( - *map(format_frequency_short, freq_range)), power_sel) + partial(self.update_tx_power, freq_range) + ) + self._power_layout.addRow( + "TX power {}..{}".format( + *map(format_frequency_short, freq_range) + ), + power_sel, + ) def update_band(self, apply: bool = False): logger.debug("update_band(%s)", apply) index_start = self.band_list.model().index( - self.band_list.currentIndex(), 1) + self.band_list.currentIndex(), 1 + ) index_stop = self.band_list.model().index( - self.band_list.currentIndex(), 2) - start = int(self.band_list.model().data( - index_start, QtCore.Qt.ItemDataRole).value()) - stop = int(self.band_list.model().data( - index_stop, QtCore.Qt.ItemDataRole).value()) + self.band_list.currentIndex(), 2 + ) + start = int( + self.band_list.model() + .data(index_start, QtCore.Qt.ItemDataRole) + .value() + ) + stop = int( + self.band_list.model() + .data(index_stop, QtCore.Qt.ItemDataRole) + .value() + ) if self.padding > 0: span = stop - start @@ -209,33 +241,37 @@ class SweepSettingsWindow(QtWidgets.QWidget): self.band_label.setText( f"Sweep span: {format_frequency_short(start)}" - f" to {format_frequency_short(stop)}") + f" to {format_frequency_short(stop)}" + ) if not apply: return self.app.sweep_control.input_start.setText( - format_frequency_sweep(start)) - self.app.sweep_control.input_end.setText( - format_frequency_sweep(stop)) + format_frequency_sweep(start) + ) + self.app.sweep_control.input_end.setText(format_frequency_sweep(stop)) self.app.sweep_control.input_end.textEdited.emit( - self.app.sweep_control.input_end.text()) + self.app.sweep_control.input_end.text() + ) - def update_attenuator(self, value: 'QtWidgets.QLineEdit'): + def update_attenuator(self, value: "QtWidgets.QLineEdit"): try: att = float(value.text()) assert att >= 0 except (ValueError, AssertionError): - logger.warning("Values for attenuator are absolute and with no" - " minus sign, resetting.") + logger.warning( + "Values for attenuator are absolute and with no" + " minus sign, resetting." + ) att = 0 logger.debug("Attenuator %sdB inline with S21 input", att) value.setText(str(att)) self.app.s21att = att - def update_averaging(self, - averages: 'QtWidgets.QLineEdit', - truncs: 'QtWidgets.QLineEdit'): + def update_averaging( + self, averages: "QtWidgets.QLineEdit", truncs: "QtWidgets.QLineEdit" + ): try: amount = int(averages.text()) truncates = int(truncs.text()) @@ -257,7 +293,7 @@ class SweepSettingsWindow(QtWidgets.QWidget): with self.app.sweep.lock: self.app.sweep.properties.logarithmic = logarithmic - def update_mode(self, mode: 'SweepMode'): + def update_mode(self, mode: "SweepMode"): logger.debug("update_mode(%s)", mode) with self.app.sweep.lock: self.app.sweep.properties.mode = mode diff --git a/src/NanoVNASaver/Windows/TDR.py b/src/NanoVNASaver/Windows/TDR.py index 84bd052..f94a746 100644 --- a/src/NanoVNASaver/Windows/TDR.py +++ b/src/NanoVNASaver/Windows/TDR.py @@ -20,6 +20,7 @@ import logging import math import numpy as np + # pylint: disable=import-error, no-name-in-module from scipy.signal import convolve from scipy.constants import speed_of_light @@ -48,9 +49,9 @@ CABLE_PARAMETERS = ( ("RG-8/U (Shireen RFC®400 Low Loss) (0.86)", 0.86), ("RG-8X (Belden 9258) (0.82)", 0.82), # Next three added by EKZ, KC3KZ, from measurement of actual cable - ("RG-8X (Wireman \"Super 8\" CQ106) (0.81)", 0.81), - ("RG-8X (Wireman \"MINI-8 Lo-Loss\" CQ118) (0.82)", 0.82), - ("RG-58 (Wireman \"CQ 58 Lo-Loss Flex\" CQ129FF) (0.79)", 0.79), + ('RG-8X (Wireman "Super 8" CQ106) (0.81)', 0.81), + ('RG-8X (Wireman "MINI-8 Lo-Loss" CQ118) (0.82)', 0.82), + ('RG-58 (Wireman "CQ 58 Lo-Loss Flex" CQ129FF) (0.79)', 0.79), ("RG-11/U 75\N{OHM SIGN} Foam HDPE (Belden 9292) (0.84)", 0.84), ("RG-58/U 52\N{OHM SIGN} PE (Belden 9201) (0.66)", 0.66), ("RG-58A/U 54\N{OHM SIGN} Foam (Belden 8219) (0.73)", 0.73), @@ -92,7 +93,8 @@ class TDRWindow(QtWidgets.QWidget): for cable_name, velocity in CABLE_PARAMETERS: self.tdr_velocity_dropdown.addItem(cable_name, velocity) self.tdr_velocity_dropdown.insertSeparator( - self.tdr_velocity_dropdown.count()) + self.tdr_velocity_dropdown.count() + ) self.tdr_velocity_dropdown.addItem("Custom", -1) self.tdr_velocity_dropdown.setCurrentIndex(1) # Default to PE (0.66) self.tdr_velocity_dropdown.currentIndexChanged.connect(self.updateTDR) @@ -121,7 +123,8 @@ class TDRWindow(QtWidgets.QWidget): else: self.tdr_velocity_input.setDisabled(True) self.tdr_velocity_input.setText( - str(self.tdr_velocity_dropdown.currentData())) + str(self.tdr_velocity_dropdown.currentData()) + ) try: v = float(self.tdr_velocity_input.text()) @@ -142,8 +145,7 @@ class TDRWindow(QtWidgets.QWidget): step = np.ones(FFT_POINTS) step_response = convolve(td, step) - self.step_response_Z = 50 * ( - 1 + step_response) / (1 - step_response) + self.step_response_Z = 50 * (1 + step_response) / (1 - step_response) time_axis = np.linspace(0, 1 / step_size, FFT_POINTS) self.distance_axis = time_axis * v * speed_of_light diff --git a/src/NanoVNASaver/Windows/__init__.py b/src/NanoVNASaver/Windows/__init__.py index 24b6db6..5af38b8 100644 --- a/src/NanoVNASaver/Windows/__init__.py +++ b/src/NanoVNASaver/Windows/__init__.py @@ -9,16 +9,17 @@ from .MarkerSettings import MarkerSettingsWindow from .Screenshot import ScreenshotWindow from .SweepSettings import SweepSettingsWindow from .TDR import TDRWindow + __all__ = [ - 'AboutWindow', - 'AnalysisWindow', - 'BandsWindow', - 'CalibrationWindow', - 'DeviceSettingsWindow', - 'DisplaySettingsWindow', - 'FilesWindow', - 'MarkerSettingsWindow', - 'ScreenshotWindow', - 'SweepSettingsWindow', - 'TDRWindow', + "AboutWindow", + "AnalysisWindow", + "BandsWindow", + "CalibrationWindow", + "DeviceSettingsWindow", + "DisplaySettingsWindow", + "FilesWindow", + "MarkerSettingsWindow", + "ScreenshotWindow", + "SweepSettingsWindow", + "TDRWindow", ] diff --git a/src/NanoVNASaver/__init__.py b/src/NanoVNASaver/__init__.py index 5ad22a2..73f2a09 100644 --- a/src/NanoVNASaver/__init__.py +++ b/src/NanoVNASaver/__init__.py @@ -2,9 +2,15 @@ import sys if sys.version_info[:2] >= (3, 8): # TODO: Import directly (no need for conditional) when `python_requires = >= 3.8` - from importlib.metadata import PackageNotFoundError, version # pragma: no cover + from importlib.metadata import ( + PackageNotFoundError, + version, + ) # pragma: no cover else: - from importlib_metadata import PackageNotFoundError, version # pragma: no cover + from importlib_metadata import ( + PackageNotFoundError, + version, + ) # pragma: no cover try: # Change here if project is renamed and does not equal the package name diff --git a/src/NanoVNASaver/__main__.py b/src/NanoVNASaver/__main__.py index 944837c..a45022f 100644 --- a/src/NanoVNASaver/__main__.py +++ b/src/NanoVNASaver/__main__.py @@ -40,19 +40,27 @@ from NanoVNASaver.Touchstone import Touchstone def main(): parser = argparse.ArgumentParser( description=__doc__, - formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("-d", "--debug", action="store_true", - help="Set loglevel to debug") - parser.add_argument("-D", "--debug-file", - help="File to write debug logging output to") - parser.add_argument("-f", "--file", - help="Touchstone file to load as sweep for off" - " device usage") - parser.add_argument("-r", "--ref-file", - help="Touchstone file to load as reference for off" - " device usage") - parser.add_argument("--version", action="version", - version=f"NanoVNASaver {VERSION}") + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "-d", "--debug", action="store_true", help="Set loglevel to debug" + ) + parser.add_argument( + "-D", "--debug-file", help="File to write debug logging output to" + ) + parser.add_argument( + "-f", + "--file", + help="Touchstone file to load as sweep for off" " device usage", + ) + parser.add_argument( + "-r", + "--ref-file", + help="Touchstone file to load as reference for off" " device usage", + ) + parser.add_argument( + "--version", action="version", version=f"NanoVNASaver {VERSION}" + ) args = parser.parse_args() console_log_level = logging.WARNING @@ -69,7 +77,8 @@ def main(): ch = logging.StreamHandler() ch.setLevel(console_log_level) formatter = logging.Formatter( - '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) ch.setFormatter(formatter) logger.addHandler(ch) @@ -81,8 +90,7 @@ def main(): logger.info("Startup...") - QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, - True) + QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) app = QtWidgets.QApplication(sys.argv) window = NanoVNASaver() window.show() @@ -104,5 +112,5 @@ def main(): raise exc -if __name__ == '__main__': +if __name__ == "__main__": main()