used black for reformatting/lintig

pull/611/head
Holger Müller 2023-03-08 09:40:39 +01:00
rodzic b0110002ec
commit 50b540a832
77 zmienionych plików z 2956 dodań i 1842 usunięć

Wyświetl plik

@ -28,9 +28,10 @@ try:
from NanoVNASaver.__main__ import main from NanoVNASaver.__main__ import main
except ModuleNotFoundError: except ModuleNotFoundError:
import sys import sys
sys.path.append('src')
sys.path.append("src")
from NanoVNASaver.__main__ import main from NanoVNASaver.__main__ import main
if __name__ == '__main__': if __name__ == "__main__":
main() main()

Wyświetl plik

@ -20,7 +20,8 @@
VERSION = "0.6.0-pre" VERSION = "0.6.0-pre"
VERSION_URL = ( VERSION_URL = (
"https://raw.githubusercontent.com/" "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_URL = "https://github.com/NanoVNA-Saver/nanovna-saver"
INFO = f"""NanoVNASaver {VERSION} INFO = f"""NanoVNASaver {VERSION}

Wyświetl plik

@ -35,6 +35,7 @@ class MagLoopAnalysis(VSWRAnalysis):
Useful for tuning magloop. Useful for tuning magloop.
""" """
max_dips_shown = 1 max_dips_shown = 1
vswr_bandwith_value = 2.56 # -3 dB ?!? vswr_bandwith_value = 2.56 # -3 dB ?!?
@ -56,12 +57,17 @@ class MagLoopAnalysis(VSWRAnalysis):
if self.min_freq is None: if self.min_freq is None:
self.min_freq = new_start self.min_freq = new_start
self.max_freq = new_end self.max_freq = new_end
logger.debug("setting hard limits to %s - %s", logger.debug(
self.min_freq, self.max_freq) "setting hard limits to %s - %s", self.min_freq, self.max_freq
)
if len(self.minimums) > 1: if len(self.minimums) > 1:
self.layout.addRow("", QtWidgets.QLabel( self.layout.addRow(
"Multiple minimums, not magloop or try to lower VSWR limit")) "",
QtWidgets.QLabel(
"Multiple minimums, not magloop or try to lower VSWR limit"
),
)
return return
if len(self.minimums) == 1: if len(self.minimums) == 1:
@ -73,22 +79,25 @@ class MagLoopAnalysis(VSWRAnalysis):
logger.debug(" Zoom to %s-%s", new_start, new_end) logger.debug(" Zoom to %s-%s", new_start, new_end)
elif self.vswr_limit_value == self.vswr_bandwith_value: elif self.vswr_limit_value == self.vswr_bandwith_value:
Q = self.app.data.s11[lowest].freq / \ Q = self.app.data.s11[lowest].freq / (
(self.app.data.s11[end].freq - self.app.data.s11[end].freq - self.app.data.s11[start].freq
self.app.data.s11[start].freq) )
self.layout.addRow("Q", QtWidgets.QLabel(f"{int(Q)}")) self.layout.addRow("Q", QtWidgets.QLabel(f"{int(Q)}"))
new_start = self.app.data.s11[start].freq - self.bandwith new_start = self.app.data.s11[start].freq - self.bandwith
new_end = self.app.data.s11[end].freq + self.bandwith new_end = self.app.data.s11[end].freq + self.bandwith
logger.debug("Single Spot, new scan on %s-%s", logger.debug(
new_start, new_end) "Single Spot, new scan on %s-%s", new_start, new_end
)
if self.vswr_limit_value > self.vswr_bandwith_value: if self.vswr_limit_value > self.vswr_bandwith_value:
self.vswr_limit_value = max( 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) self.input_vswr_limit.setValue(self.vswr_limit_value)
logger.debug( logger.debug(
"found higher minimum, lowering vswr search to %s", "found higher minimum, lowering vswr search to %s",
self.vswr_limit_value) self.vswr_limit_value,
)
else: else:
new_start = new_start - 5 * self.bandwith new_start = new_start - 5 * self.bandwith
new_end = new_end + 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) self.input_vswr_limit.setValue(self.vswr_limit_value)
logger.debug( logger.debug(
"no minimum found, looking for higher value %s", "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_start = max(self.min_freq, new_start)
new_end = min(self.max_freq, new_end) new_end = min(self.max_freq, new_end)
logger.debug("next search will be %s - %s for vswr %s", logger.debug(
new_start, "next search will be %s - %s for vswr %s",
new_end, new_start,
self.vswr_limit_value) new_end,
self.vswr_limit_value,
)
self.app.sweep_control.set_start(new_start) self.app.sweep_control.set_start(new_start)
self.app.sweep_control.set_end(new_end) self.app.sweep_control.set_end(new_end)

Wyświetl plik

@ -33,42 +33,52 @@ class BandPassAnalysis(Analysis):
def __init__(self, app): def __init__(self, app):
super().__init__(app) super().__init__(app)
for label in ('octave_l', 'octave_r', 'decade_l', 'decade_r', for label in (
'freq_center', 'span_3.0dB', 'span_6.0dB', 'q_factor'): "octave_l",
"octave_r",
"decade_l",
"decade_r",
"freq_center",
"span_3.0dB",
"span_6.0dB",
"q_factor",
):
self.label[label] = QtWidgets.QLabel() self.label[label] = QtWidgets.QLabel()
for attn in CUTOFF_VALS: for attn in CUTOFF_VALS:
self.label[f"{attn:.1f}dB_l"] = QtWidgets.QLabel() self.label[f"{attn:.1f}dB_l"] = QtWidgets.QLabel()
self.label[f"{attn:.1f}dB_r"] = QtWidgets.QLabel() self.label[f"{attn:.1f}dB_r"] = QtWidgets.QLabel()
layout = self.layout layout = self.layout
layout.addRow(self.label['titel']) layout.addRow(self.label["titel"])
layout.addRow( layout.addRow(
QtWidgets.QLabel( QtWidgets.QLabel(
f"Please place {self.app.markers[0].name}" f"Please place {self.app.markers[0].name}"
f" in the filter passband.")) f" in the filter passband."
layout.addRow("Result:", self.label['result']) )
)
layout.addRow("Result:", self.label["result"])
layout.addRow(QtWidgets.QLabel("")) layout.addRow(QtWidgets.QLabel(""))
layout.addRow("Center frequency:", self.label['freq_center']) layout.addRow("Center frequency:", self.label["freq_center"])
layout.addRow("Bandwidth (-3 dB):", self.label['span_3.0dB']) layout.addRow("Bandwidth (-3 dB):", self.label["span_3.0dB"])
layout.addRow("Quality factor:", self.label['q_factor']) layout.addRow("Quality factor:", self.label["q_factor"])
layout.addRow("Bandwidth (-6 dB):", self.label['span_6.0dB']) layout.addRow("Bandwidth (-6 dB):", self.label["span_6.0dB"])
layout.addRow(QtWidgets.QLabel("")) layout.addRow(QtWidgets.QLabel(""))
layout.addRow(QtWidgets.QLabel("Lower side:")) layout.addRow(QtWidgets.QLabel("Lower side:"))
layout.addRow("Cutoff frequency:", self.label['3.0dB_l']) layout.addRow("Cutoff frequency:", self.label["3.0dB_l"])
layout.addRow("-6 dB point:", self.label['6.0dB_l']) layout.addRow("-6 dB point:", self.label["6.0dB_l"])
layout.addRow("-60 dB point:", self.label['60.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["octave_l"])
layout.addRow("Roll-off:", self.label['decade_l']) layout.addRow("Roll-off:", self.label["decade_l"])
layout.addRow(QtWidgets.QLabel("")) layout.addRow(QtWidgets.QLabel(""))
layout.addRow(QtWidgets.QLabel("Upper side:")) layout.addRow(QtWidgets.QLabel("Upper side:"))
layout.addRow("Cutoff frequency:", self.label['3.0dB_r']) layout.addRow("Cutoff frequency:", self.label["3.0dB_r"])
layout.addRow("-6 dB point:", self.label['6.0dB_r']) layout.addRow("-6 dB point:", self.label["6.0dB_r"])
layout.addRow("-60 dB point:", self.label['60.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["octave_r"])
layout.addRow("Roll-off:", self.label['decade_r']) layout.addRow("Roll-off:", self.label["decade_r"])
self.set_titel("Band pass filter analysis") self.set_titel("Band pass filter analysis")
@ -103,72 +113,90 @@ class BandPassAnalysis(Analysis):
self.derive_60dB(cutoff_pos, cutoff_freq) self.derive_60dB(cutoff_pos, cutoff_freq)
result = { result = {
'span_3.0dB': cutoff_freq['3.0dB_r'] - cutoff_freq['3.0dB_l'], "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'], "span_6.0dB": cutoff_freq["6.0dB_r"] - cutoff_freq["6.0dB_l"],
'freq_center': "freq_center": math.sqrt(
math.sqrt(cutoff_freq['3.0dB_l'] * cutoff_freq['3.0dB_r']), 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( result["octave_l"], result["decade_l"] = at.calculate_rolloff(
s21, cutoff_pos["10.0dB_l"], cutoff_pos["20.0dB_l"]) 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_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(): for label, val in cutoff_freq.items():
self.label[label].setText( self.label[label].setText(
f"{format_frequency(val)}" f"{format_frequency(val)}" f" ({cutoff_gain[label]:.1f} dB)"
f" ({cutoff_gain[label]:.1f} dB)") )
for label in ('freq_center', 'span_3.0dB', 'span_6.0dB'): for label in ("freq_center", "span_3.0dB", "span_6.0dB"):
self.label[label].setText(format_frequency(result[label])) 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.label[label].setText(f"{result[label]:.3f}dB/{label[:-2]}")
self.app.markers[0].setFrequency(f"{result['freq_center']}") self.app.markers[0].setFrequency(f"{result['freq_center']}")
self.app.markers[1].setFrequency(f"{cutoff_freq['3.0dB_l']}") self.app.markers[1].setFrequency(f"{cutoff_freq['3.0dB_l']}")
self.app.markers[2].setFrequency(f"{cutoff_freq['3.0dB_r']}") 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( logger.warning(
"Data points insufficient for true -3 dB points." "Data points insufficient for true -3 dB points."
"Cutoff gains: %fdB, %fdB", cutoff_gain['3.0dB_l'], "Cutoff gains: %fdB, %fdB",
cutoff_gain['3.0dB_r']) cutoff_gain["3.0dB_l"],
cutoff_gain["3.0dB_r"],
)
self.set_result( self.set_result(
f"Analysis complete ({len(s21)} points)\n" f"Analysis complete ({len(s21)} points)\n"
f"Insufficient data for analysis. Increase segment count.") f"Insufficient data for analysis. Increase segment count."
)
return return
self.set_result(f"Analysis complete ({len(s21)} points)") self.set_result(f"Analysis complete ({len(s21)} points)")
def derive_60dB(self, def derive_60dB(
cutoff_pos: Dict[str, int], self, cutoff_pos: Dict[str, int], cutoff_freq: Dict[str, float]
cutoff_freq: Dict[str, float]): ):
"""derive 60dB cutoff if needed an possible """derive 60dB cutoff if needed an possible
Args: Args:
cutoff_pos (Dict[str, int]) cutoff_pos (Dict[str, int])
cutoff_freq (Dict[str, float]) cutoff_freq (Dict[str, float])
""" """
if (math.isnan(cutoff_freq['60.0dB_l']) and if (
cutoff_pos['20.0dB_l'] != -1 and cutoff_pos['10.0dB_l'] != -1): math.isnan(cutoff_freq["60.0dB_l"])
cutoff_freq['60.0dB_l'] = ( and cutoff_pos["20.0dB_l"] != -1
cutoff_freq["10.0dB_l"] * and cutoff_pos["10.0dB_l"] != -1
10 ** (5 * (math.log10(cutoff_pos['20.0dB_l']) - ):
math.log10(cutoff_pos['10.0dB_l'])))) cutoff_freq["60.0dB_l"] = cutoff_freq["10.0dB_l"] * 10 ** (
if (math.isnan(cutoff_freq['60.0dB_r']) and 5
cutoff_pos['20.0dB_r'] != -1 and cutoff_pos['10.0dB_r'] != -1): * (
cutoff_freq['60.0dB_r'] = ( math.log10(cutoff_pos["20.0dB_l"])
cutoff_freq["10.0dB_r"] * - math.log10(cutoff_pos["10.0dB_l"])
10 ** (5 * (math.log10(cutoff_pos['20.0dB_r']) - )
math.log10(cutoff_pos['10.0dB_r']) )
))) 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: def find_center(self, gains: List[float]) -> int:
marker = self.app.markers[0] marker = self.app.markers[0]
if marker.location <= 0 or marker.location >= len(gains) - 1: if marker.location <= 0 or marker.location >= len(gains) - 1:
logger.debug("No valid location for %s (%s)", logger.debug(
marker.name, marker.location) "No valid location for %s (%s)", marker.name, marker.location
)
self.set_result(f"Please place {marker.name} in the passband.") self.set_result(f"Please place {marker.name} in the passband.")
return -1 return -1
@ -178,13 +206,15 @@ class BandPassAnalysis(Analysis):
return -1 return -1
return peak return peak
def find_bounderies(self, def find_bounderies(
gains: List[float], self, gains: List[float], peak: int, peak_db: float
peak: int, peak_db: float) -> Dict[str, int]: ) -> Dict[str, int]:
cutoff_pos = {} cutoff_pos = {}
for attn in CUTOFF_VALS: for attn in CUTOFF_VALS:
cutoff_pos[f"{attn:.1f}dB_l"] = at.cut_off_left( 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( cutoff_pos[f"{attn:.1f}dB_r"] = at.cut_off_right(
gains, peak, peak_db, attn) gains, peak, peak_db, attn
)
return cutoff_pos return cutoff_pos

Wyświetl plik

@ -34,11 +34,13 @@ class BandStopAnalysis(BandPassAnalysis):
def find_center(self, gains: List[float]) -> int: def find_center(self, gains: List[float]) -> int:
return max(enumerate(gains), key=lambda i: i[1])[0] return max(enumerate(gains), key=lambda i: i[1])[0]
def find_bounderies(self, def find_bounderies(
gains: List[float], self, gains: List[float], _: int, peak_db: float
_: int, peak_db: float) -> Dict[str, int]: ) -> Dict[str, int]:
cutoff_pos = {} cutoff_pos = {}
for attn in CUTOFF_VALS: 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 return cutoff_pos

Wyświetl plik

@ -35,8 +35,8 @@ class Analysis:
def __init__(self, app: QtWidgets.QWidget): def __init__(self, app: QtWidgets.QWidget):
self.app = app self.app = app
self.label: Dict[str, QtWidgets.QLabel] = { self.label: Dict[str, QtWidgets.QLabel] = {
'titel': QtWidgets.QLabel(), "titel": QtWidgets.QLabel(),
'result': QtWidgets.QLabel(), "result": QtWidgets.QLabel(),
} }
self.layout = QtWidgets.QFormLayout() self.layout = QtWidgets.QFormLayout()
self._widget = QtWidgets.QWidget() self._widget = QtWidgets.QWidget()
@ -53,7 +53,7 @@ class Analysis:
label.clear() label.clear()
def set_result(self, text): def set_result(self, text):
self.label['result'].setText(text) self.label["result"].setText(text)
def set_titel(self, text): def set_titel(self, text):
self.label['titel'].setText(text) self.label["titel"].setText(text)

Wyświetl plik

@ -23,10 +23,14 @@ from PyQt5 import QtWidgets
import NanoVNASaver.AnalyticTools as at import NanoVNASaver.AnalyticTools as at
from NanoVNASaver.Analysis.ResonanceAnalysis import ( from NanoVNASaver.Analysis.ResonanceAnalysis import (
ResonanceAnalysis, format_resistence_neg ResonanceAnalysis,
format_resistence_neg,
) )
from NanoVNASaver.Formatting import ( from NanoVNASaver.Formatting import (
format_frequency, format_complex_imp, format_frequency_short) format_frequency,
format_complex_imp,
format_frequency_short,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -43,8 +47,8 @@ class EFHWAnalysis(ResonanceAnalysis):
def do_resonance_analysis(self): def do_resonance_analysis(self):
s11 = self.app.data.s11 s11 = self.app.data.s11
maximums = sorted( maximums = sorted(
at.maxima([d.impedance().real for d in s11], at.maxima([d.impedance().real for d in s11], threshold=500)
threshold=500)) )
extended_data = {} extended_data = {}
logger.info("TO DO: find near data") logger.info("TO DO: find near data")
for lowest in self.crossings: for lowest in self.crossings:
@ -61,12 +65,14 @@ class EFHWAnalysis(ResonanceAnalysis):
extended_data[m].update(my_data) extended_data[m].update(my_data)
else: else:
extended_data[m] = my_data extended_data[m] = my_data
fields = [("freq", format_frequency_short), fields = [
("r", format_resistence_neg), ("lambda", lambda x: round(x, 2))] ("freq", format_frequency_short),
("r", format_resistence_neg),
("lambda", lambda x: round(x, 2)),
]
if self.old_data: if self.old_data:
diff = self.compare( diff = self.compare(self.old_data[-1], extended_data, fields=fields)
self.old_data[-1], extended_data, fields=fields)
else: else:
diff = self.compare({}, extended_data, fields=fields) diff = self.compare({}, extended_data, fields=fields)
self.old_data.append(extended_data) self.old_data.append(extended_data)
@ -76,14 +82,17 @@ class EFHWAnalysis(ResonanceAnalysis):
QtWidgets.QLabel( QtWidgets.QLabel(
f" ({diff[i]['freq']})" f" ({diff[i]['freq']})"
f" {format_complex_imp(s11[idx].impedance())}" 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: if self.filename and extended_data:
with open( with open(
self.filename, 'w', newline='', encoding='utf-8' self.filename, "w", newline="", encoding="utf-8"
) as csvfile: ) as csvfile:
fieldnames = extended_data[sorted( fieldnames = extended_data[
extended_data.keys())[0]].keys() sorted(extended_data.keys())[0]
].keys()
writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader() writer.writeheader()
for idx in sorted(extended_data.keys()): for idx in sorted(extended_data.keys()):
@ -99,10 +108,11 @@ class EFHWAnalysis(ResonanceAnalysis):
:param old: :param old:
:param new: :param new:
""" """
fields = fields or [("freq", str), ] fields = fields or [
("freq", str),
]
def no_compare(): def no_compare():
return {k: "-" for k, _ in fields} return {k: "-" for k, _ in fields}
old_idx = sorted(old.keys()) old_idx = sorted(old.keys())
@ -113,8 +123,9 @@ class EFHWAnalysis(ResonanceAnalysis):
i_tot = max(len(old_idx), len(new_idx)) i_tot = max(len(old_idx), len(new_idx))
if i_max != i_tot: if i_max != i_tot:
logger.warning("resonances changed from %s to %s", logger.warning(
len(old_idx), len(new_idx)) "resonances changed from %s to %s", len(old_idx), len(new_idx)
)
split = 0 split = 0
max_delta_f = 1_000_000 max_delta_f = 1_000_000
@ -135,15 +146,19 @@ class EFHWAnalysis(ResonanceAnalysis):
logger.debug("Deltas %s", diff[i]) logger.debug("Deltas %s", diff[i])
continue continue
logger.debug("can't compare, %s is too much ", logger.debug(
format_frequency(delta_f)) "can't compare, %s is too much ", format_frequency(delta_f)
)
if delta_f > 0: if delta_f > 0:
logger.debug("possible missing band, ") logger.debug("possible missing band, ")
if len(old_idx) > (i + split + 1): if len(old_idx) > (i + split + 1):
if (abs(new[k]["freq"] - if (
old[old_idx[i + split + 1]]["freq"]) < abs(
max_delta_f): new[k]["freq"] - old[old_idx[i + split + 1]]["freq"]
)
< max_delta_f
):
logger.debug("new is missing band, compare next ") logger.debug("new is missing band, compare next ")
split += 1 split += 1
# FIXME: manage 2 or more band missing ?!? # FIXME: manage 2 or more band missing ?!?

Wyświetl plik

@ -41,9 +41,12 @@ class HighPassAnalysis(Analysis):
layout = self.layout layout = self.layout
layout.addRow(self.label["titel"]) layout.addRow(self.label["titel"])
layout.addRow(QtWidgets.QLabel( layout.addRow(
f"Please place {self.app.markers[0].name}" QtWidgets.QLabel(
f" in the filter passband.")) f"Please place {self.app.markers[0].name}"
f" in the filter passband."
)
)
layout.addRow("Result:", self.label["result"]) layout.addRow("Result:", self.label["result"])
layout.addRow("Cutoff frequency:", self.label["3.0dB"]) layout.addRow("Cutoff frequency:", self.label["3.0dB"])
layout.addRow("-6 dB point:", self.label["6.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["octave"])
layout.addRow("Roll-off:", self.label["decade"]) layout.addRow("Roll-off:", self.label["decade"])
self.set_titel('Highpass analysis') self.set_titel("Highpass analysis")
def runAnalysis(self): def runAnalysis(self):
if not self.app.data.s21: if not self.app.data.s21:
@ -81,25 +84,28 @@ class HighPassAnalysis(Analysis):
logger.debug("Cuttoff gains: %s", cutoff_gain) logger.debug("Cuttoff gains: %s", cutoff_gain)
octave, decade = at.calculate_rolloff( 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: if cutoff_gain["3.0dB"] < -4:
logger.debug("Cutoff frequency found at %f dB" logger.debug(
" - insufficient data points for true -3 dB point.", "Cutoff frequency found at %f dB"
cutoff_gain) " - insufficient data points for true -3 dB point.",
logger.debug("Found true cutoff frequency at %d", cutoff_freq['3.0dB']) cutoff_gain,
)
logger.debug("Found true cutoff frequency at %d", cutoff_freq["3.0dB"])
for label, val in cutoff_freq.items(): for label, val in cutoff_freq.items():
self.label[label].setText( self.label[label].setText(
f"{format_frequency(val)}" f"{format_frequency(val)}" f" ({cutoff_gain[label]:.1f} dB)"
f" ({cutoff_gain[label]:.1f} dB)") )
self.label['octave'].setText(f'{octave:.3f}dB/octave') self.label["octave"].setText(f"{octave:.3f}dB/octave")
self.label['decade'].setText(f'{decade:.3f}dB/decade') self.label["decade"].setText(f"{decade:.3f}dB/decade")
self.app.markers[0].setFrequency(str(s21[peak].freq)) self.app.markers[0].setFrequency(str(s21[peak].freq))
self.app.markers[1].setFrequency(str(cutoff_freq['3.0dB'])) self.app.markers[1].setFrequency(str(cutoff_freq["3.0dB"]))
self.app.markers[2].setFrequency(str(cutoff_freq['6.0dB'])) self.app.markers[2].setFrequency(str(cutoff_freq["6.0dB"]))
self.set_result(f"Analysis complete ({len(s21)}) points)") self.set_result(f"Analysis complete ({len(s21)}) points)")
@ -111,11 +117,10 @@ class HighPassAnalysis(Analysis):
return -1 return -1
return at.center_from_idx(gains, marker.location) return at.center_from_idx(gains, marker.location)
def find_cutoffs(self, def find_cutoffs(
gains: List[float], self, gains: List[float], peak: int, peak_db: float
peak: int, peak_db: float) -> Dict[str, int]: ) -> Dict[str, int]:
return { return {
f"{attn:.1f}dB": at.cut_off_left( f"{attn:.1f}dB": at.cut_off_left(gains, peak, peak_db, attn)
gains, peak, peak_db, attn)
for attn in CUTOFF_VALS for attn in CUTOFF_VALS
} }

Wyświetl plik

@ -30,13 +30,12 @@ class LowPassAnalysis(HighPassAnalysis):
def __init__(self, app): def __init__(self, app):
super().__init__(app) super().__init__(app)
self.set_titel('Lowpass filter analysis') self.set_titel("Lowpass filter analysis")
def find_cutoffs(self, def find_cutoffs(
gains: List[float], self, gains: List[float], peak: int, peak_db: float
peak: int, peak_db: float) -> Dict[str, int]: ) -> Dict[str, int]:
return { return {
f"{attn:.1f}dB": at.cut_off_right( f"{attn:.1f}dB": at.cut_off_right(gains, peak, peak_db, attn)
gains, peak, peak_db, attn)
for attn in CUTOFF_VALS for attn in CUTOFF_VALS
} }

Wyświetl plik

@ -20,12 +20,14 @@ import logging
from PyQt5 import QtWidgets from PyQt5 import QtWidgets
import numpy as np import numpy as np
# pylint: disable=import-error, no-name-in-module # pylint: disable=import-error, no-name-in-module
from scipy.signal import find_peaks, peak_prominences from scipy.signal import find_peaks, peak_prominences
from NanoVNASaver.Analysis.Base import QHLine from NanoVNASaver.Analysis.Base import QHLine
from NanoVNASaver.Analysis.SimplePeakSearchAnalysis import ( from NanoVNASaver.Analysis.SimplePeakSearchAnalysis import (
SimplePeakSearchAnalysis) SimplePeakSearchAnalysis,
)
from NanoVNASaver.Formatting import format_frequency_short from NanoVNASaver.Formatting import format_frequency_short
@ -34,7 +36,6 @@ logger = logging.getLogger(__name__)
class PeakSearchAnalysis(SimplePeakSearchAnalysis): class PeakSearchAnalysis(SimplePeakSearchAnalysis):
def __init__(self, app): def __init__(self, app):
super().__init__(app) super().__init__(app)
@ -48,7 +49,7 @@ class PeakSearchAnalysis(SimplePeakSearchAnalysis):
self.layout.addRow(QtWidgets.QLabel("<b>Results</b>")) self.layout.addRow(QtWidgets.QLabel("<b>Results</b>"))
self.results_header = self.layout.rowCount() self.results_header = self.layout.rowCount()
self.set_titel('Peak search') self.set_titel("Peak search")
def runAnalysis(self): def runAnalysis(self):
if not self.app.data.s11: if not self.app.data.s11:
@ -59,14 +60,14 @@ class PeakSearchAnalysis(SimplePeakSearchAnalysis):
data, fmt_fnc = self.data_and_format() data, fmt_fnc = self.data_and_format()
inverted = False inverted = False
if self.button['peak_l'].isChecked(): if self.button["peak_l"].isChecked():
inverted = True inverted = True
peaks, _ = find_peaks( peaks, _ = find_peaks(
-np.array(data), width=3, distance=3, prominence=1) -np.array(data), width=3, distance=3, prominence=1
)
else: else:
self.button['peak_h'].setChecked(True) self.button["peak_h"].setChecked(True)
peaks, _ = find_peaks( peaks, _ = find_peaks(data, width=3, distance=3, prominence=1)
data, width=3, distance=3, prominence=1)
# Having found the peaks, get the prominence data # Having found the peaks, get the prominence data
for i, p in np.ndenumerate(peaks): for i, p in np.ndenumerate(peaks):
@ -89,19 +90,24 @@ class PeakSearchAnalysis(SimplePeakSearchAnalysis):
f"Freq: {format_frequency_short(s11[pos].freq)}", f"Freq: {format_frequency_short(s11[pos].freq)}",
QtWidgets.QLabel( QtWidgets.QLabel(
f" Value: {fmt_fnc(-data[pos] if inverted else data[pos])}" 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): if count > len(self.app.markers):
logger.warning("More peaks found than there are markers") logger.warning("More peaks found than there are markers")
for i in range(min(count, len(self.app.markers))): for i in range(min(count, len(self.app.markers))):
self.app.markers[i].setFrequency( self.app.markers[i].setFrequency(
str(s11[peaks[indices[i]]].freq)) str(s11[peaks[indices[i]]].freq)
)
def reset(self): def reset(self):
super().reset() super().reset()
logger.debug("Results start at %d, out of %d", logger.debug(
self.results_header, self.layout.rowCount()) "Results start at %d, out of %d",
self.results_header,
self.layout.rowCount(),
)
for _ in range(self.results_header, self.layout.rowCount()): for _ in range(self.results_header, self.layout.rowCount()):
logger.debug("deleting %s", self.layout.rowCount()) logger.debug("deleting %s", self.layout.rowCount())
self.layout.removeRow(self.layout.rowCount() - 1) self.layout.removeRow(self.layout.rowCount() - 1)

Wyświetl plik

@ -25,9 +25,7 @@ from PyQt5 import QtWidgets
import NanoVNASaver.AnalyticTools as at import NanoVNASaver.AnalyticTools as at
from NanoVNASaver.Analysis.Base import Analysis, QHLine from NanoVNASaver.Analysis.Base import Analysis, QHLine
from NanoVNASaver.Formatting import ( from NanoVNASaver.Formatting import format_frequency, format_resistance
format_frequency, format_complex_imp,
format_resistance)
from NanoVNASaver.RFTools import reflection_coefficient from NanoVNASaver.RFTools import reflection_coefficient
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -44,7 +42,6 @@ def vswr_transformed(z, ratio=49) -> float:
class ResonanceAnalysis(Analysis): class ResonanceAnalysis(Analysis):
def __init__(self, app): def __init__(self, app):
super().__init__(app) super().__init__(app)
self.crossings: List[int] = [] self.crossings: List[int] = []
@ -72,10 +69,8 @@ class ResonanceAnalysis(Analysis):
"impedance": s11[index].impedance(), "impedance": s11[index].impedance(),
"vswr": s11[index].vswr, "vswr": s11[index].vswr,
} }
my_data["vswr_49"] = vswr_transformed( my_data["vswr_49"] = vswr_transformed(my_data["impedance"], 49)
my_data["impedance"], 49) my_data["vswr_4"] = vswr_transformed(my_data["impedance"], 4)
my_data["vswr_4"] = vswr_transformed(
my_data["impedance"], 4)
my_data["r"] = my_data["impedance"].real my_data["r"] = my_data["impedance"].real
my_data["x"] = my_data["impedance"].imag my_data["x"] = my_data["impedance"].imag
@ -83,24 +78,28 @@ class ResonanceAnalysis(Analysis):
def runAnalysis(self): def runAnalysis(self):
self.reset() self.reset()
self.filename = os.path.join( self.filename = (
"/tmp/", f"{self.input_description.text()}.csv" os.path.join("/tmp/", f"{self.input_description.text()}.csv")
) if self.input_description.text() else "" if self.input_description.text()
else ""
)
results_header = self.layout.indexOf(self.results_label) results_header = self.layout.indexOf(self.results_label)
logger.debug("Results start at %d, out of %d", logger.debug(
results_header, self.layout.rowCount()) "Results start at %d, out of %d",
results_header,
self.layout.rowCount(),
)
for _ in range(results_header, self.layout.rowCount()): for _ in range(results_header, self.layout.rowCount()):
self.layout.removeRow(self.layout.rowCount() - 1) self.layout.removeRow(self.layout.rowCount() - 1)
self.crossings = sorted( self.crossings = sorted(
set(at.zero_crossings([d.phase for d in self.app.data.s11]))) set(at.zero_crossings([d.phase for d in self.app.data.s11]))
logger.debug("Found %d sections ", )
len(self.crossings)) logger.debug("Found %d sections ", len(self.crossings))
if not self.crossings: if not self.crossings:
self.layout.addRow(QtWidgets.QLabel( self.layout.addRow(QtWidgets.QLabel("No resonance found"))
"No resonance found"))
return return
self self
@ -111,14 +110,18 @@ class ResonanceAnalysis(Analysis):
extended_data = [] extended_data = []
for crossing in self.crossings: for crossing in self.crossings:
extended_data.append(self._get_data(crossing)) extended_data.append(self._get_data(crossing))
self.layout.addRow("Resonance", QtWidgets.QLabel( self.layout.addRow(
format_frequency(self.app.data.s11[crossing].freq))) "Resonance",
QtWidgets.QLabel(
format_frequency(self.app.data.s11[crossing].freq)
),
)
self.layout.addWidget(QHLine()) self.layout.addWidget(QHLine())
# Remove the final separator line # Remove the final separator line
self.layout.removeRow(self.layout.rowCount() - 1) self.layout.removeRow(self.layout.rowCount() - 1)
if self.filename and extended_data: if self.filename and extended_data:
with open( with open(
self.filename, 'w', encoding='utf-8', newline='' self.filename, "w", encoding="utf-8", newline=""
) as csvfile: ) as csvfile:
fieldnames = extended_data[0].keys() fieldnames = extended_data[0].keys()
writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

Wyświetl plik

@ -24,7 +24,11 @@ import numpy as np
from NanoVNASaver.Analysis.Base import Analysis, QHLine from NanoVNASaver.Analysis.Base import Analysis, QHLine
from NanoVNASaver.Formatting import ( from NanoVNASaver.Formatting import (
format_frequency, format_gain, format_resistance, format_vswr) format_frequency,
format_gain,
format_resistance,
format_vswr,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -33,51 +37,51 @@ class SimplePeakSearchAnalysis(Analysis):
def __init__(self, app): def __init__(self, app):
super().__init__(app) super().__init__(app)
self.label['peak_freq'] = QtWidgets.QLabel() self.label["peak_freq"] = QtWidgets.QLabel()
self.label['peak_db'] = QtWidgets.QLabel() self.label["peak_db"] = QtWidgets.QLabel()
self.button = { self.button = {
'vswr': QtWidgets.QRadioButton("VSWR"), "vswr": QtWidgets.QRadioButton("VSWR"),
'resistance': QtWidgets.QRadioButton("Resistance"), "resistance": QtWidgets.QRadioButton("Resistance"),
'reactance': QtWidgets.QRadioButton("Reactance"), "reactance": QtWidgets.QRadioButton("Reactance"),
'gain': QtWidgets.QRadioButton("S21 Gain"), "gain": QtWidgets.QRadioButton("S21 Gain"),
'peak_h': QtWidgets.QRadioButton("Highest value"), "peak_h": QtWidgets.QRadioButton("Highest value"),
'peak_l': QtWidgets.QRadioButton("Lowest value"), "peak_l": QtWidgets.QRadioButton("Lowest value"),
'move_marker': QtWidgets.QCheckBox() "move_marker": QtWidgets.QCheckBox(),
} }
self.button['gain'].setChecked(True) self.button["gain"].setChecked(True)
self.button['peak_h'].setChecked(True) self.button["peak_h"].setChecked(True)
self.btn_group = { self.btn_group = {
'data': QtWidgets.QButtonGroup(), "data": QtWidgets.QButtonGroup(),
'peak': QtWidgets.QButtonGroup(), "peak": QtWidgets.QButtonGroup(),
} }
for btn in ('vswr', 'resistance', 'reactance', 'gain'): for btn in ("vswr", "resistance", "reactance", "gain"):
self.btn_group['data'].addButton(self.button[btn]) 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_h"])
self.btn_group['peak'].addButton(self.button['peak_l']) self.btn_group["peak"].addButton(self.button["peak_l"])
layout = self.layout layout = self.layout
layout.addRow(self.label['titel']) layout.addRow(self.label["titel"])
layout.addRow(QHLine()) layout.addRow(QHLine())
layout.addRow(QtWidgets.QLabel("<b>Settings</b>")) layout.addRow(QtWidgets.QLabel("<b>Settings</b>"))
layout.addRow("Data source", self.button['vswr']) layout.addRow("Data source", self.button["vswr"])
layout.addRow("", self.button['resistance']) layout.addRow("", self.button["resistance"])
layout.addRow("", self.button['reactance']) layout.addRow("", self.button["reactance"])
layout.addRow("", self.button['gain']) layout.addRow("", self.button["gain"])
layout.addRow(QHLine()) layout.addRow(QHLine())
layout.addRow("Peak type", self.button['peak_h']) layout.addRow("Peak type", self.button["peak_h"])
layout.addRow("", self.button['peak_l']) layout.addRow("", self.button["peak_l"])
layout.addRow(QHLine()) 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(QHLine())
layout.addRow(self.label['result']) layout.addRow(self.label["result"])
layout.addRow("Peak frequency:", self.label['peak_freq']) layout.addRow("Peak frequency:", self.label["peak_freq"])
layout.addRow("Peak value:", self.label['peak_db']) layout.addRow("Peak value:", self.label["peak_db"])
self.set_titel('Simple peak search') self.set_titel("Simple peak search")
def runAnalysis(self): def runAnalysis(self):
if not self.app.data.s11: if not self.app.data.s11:
@ -86,16 +90,16 @@ class SimplePeakSearchAnalysis(Analysis):
s11 = self.app.data.s11 s11 = self.app.data.s11
data, fmt_fnc = self.data_and_format() data, fmt_fnc = self.data_and_format()
if self.button['peak_l'].isChecked(): if self.button["peak_l"].isChecked():
idx_peak = np.argmin(data) idx_peak = np.argmin(data)
else: else:
self.button['peak_h'].setChecked(True) self.button["peak_h"].setChecked(True)
idx_peak = np.argmax(data) idx_peak = np.argmax(data)
self.label['peak_freq'].setText(format_frequency(s11[idx_peak].freq)) self.label["peak_freq"].setText(format_frequency(s11[idx_peak].freq))
self.label['peak_db'].setText(fmt_fnc(data[idx_peak])) 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}") self.app.markers[0].setFrequency(f"{s11[idx_peak].freq}")
def data_and_format(self) -> Tuple[List[float], Callable]: def data_and_format(self) -> Tuple[List[float], Callable]:
@ -103,17 +107,17 @@ class SimplePeakSearchAnalysis(Analysis):
s21 = self.app.data.s21 s21 = self.app.data.s21
if not s21: if not s21:
self.button['gain'].setEnabled(False) self.button["gain"].setEnabled(False)
if self.button['gain'].isChecked(): if self.button["gain"].isChecked():
self.button['vswr'].setChecked(True) self.button["vswr"].setChecked(True)
else: 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) 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) 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) return ([d.impedance().imag for d in s11], format_resistance)
# default # default
return ([d.vswr for d in s11], format_vswr) return ([d.vswr for d in s11], format_vswr)

Wyświetl plik

@ -64,34 +64,50 @@ class VSWRAnalysis(Analysis):
data = [d.vswr for d in s11] data = [d.vswr for d in s11]
threshold = self.input_vswr_limit.value() threshold = self.input_vswr_limit.value()
minima = sorted(at.minima(data, threshold), minima = sorted(at.minima(data, threshold), key=lambda i: data[i])[
key=lambda i: data[i])[:VSWRAnalysis.max_dips_shown] : VSWRAnalysis.max_dips_shown
]
self.minimums = minima self.minimums = minima
results_header = self.layout.indexOf(self.results_label) results_header = self.layout.indexOf(self.results_label)
logger.debug("Results start at %d, out of %d", logger.debug(
results_header, self.layout.rowCount()) "Results start at %d, out of %d",
results_header,
self.layout.rowCount(),
)
for _ in range(results_header, self.layout.rowCount()): for _ in range(results_header, self.layout.rowCount()):
self.layout.removeRow(self.layout.rowCount() - 1) self.layout.removeRow(self.layout.rowCount() - 1)
if not minima: if not minima:
self.layout.addRow(QtWidgets.QLabel( self.layout.addRow(
f"No areas found with VSWR below {format_vswr(threshold)}.")) QtWidgets.QLabel(
f"No areas found with VSWR below {format_vswr(threshold)}."
)
)
return return
for idx in minima: for idx in minima:
rng = at.take_from_idx(data, idx, lambda i: i[1] < threshold) rng = at.take_from_idx(data, idx, lambda i: i[1] < threshold)
begin, end = rng[0], rng[-1] 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( self.layout.addRow(
"Span", QtWidgets.QLabel(format_frequency( "Start", QtWidgets.QLabel(format_frequency(s11[begin].freq))
(s11[end].freq - 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.addWidget(QHLine())
self.layout.removeRow(self.layout.rowCount() - 1) self.layout.removeRow(self.layout.rowCount() - 1)

Wyświetl plik

@ -21,6 +21,7 @@ import math
from typing import Callable, List, Tuple from typing import Callable, List, Tuple
import numpy as np import numpy as np
# pylint: disable=import-error, no-name-in-module # pylint: disable=import-error, no-name-in-module
from scipy.signal import find_peaks from scipy.signal import find_peaks
@ -42,8 +43,9 @@ def zero_crossings(data: List[float]) -> List[int]:
np_data = np.array(data) np_data = np.array(data)
# start with real zeros (ignore first and last element) # start with real zeros (ignore first and last element)
real_zeros = [n for n in np.where(np_data == 0.0)[0] if real_zeros = [
n not in {0, np_data.size - 1}] 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 # now multipy elements to find change in signess
crossings = [ crossings = [
n if abs(np_data[n]) < abs(np_data[n + 1]) else n + 1 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: Returns:
List[int]: indices of maxima List[int]: indices of maxima
""" """
peaks = find_peaks( peaks = find_peaks(data, width=2, distance=3, prominence=1)[0].tolist()
data, width=2, distance=3, prominence=1)[0].tolist() return [i for i in peaks if data[i] > threshold] if threshold else peaks
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]: 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: Returns:
List[int]: indices of minima List[int]: indices of minima
""" """
bottoms = find_peaks( bottoms = find_peaks(-np.array(data), width=2, distance=3, prominence=1)[
-np.array(data), width=2, distance=3, prominence=1)[0].tolist() 0
return [ ].tolist()
i for i in bottoms if data[i] < threshold return [i for i in bottoms if data[i] < threshold] if threshold else bottoms
] if threshold else bottoms
def take_from_idx(data: List[float], def take_from_idx(
idx: int, data: List[float], idx: int, predicate: Callable
predicate: Callable) -> List[int]: ) -> List[int]:
"""take_from_center """take_from_center
Args: Args:
@ -99,18 +97,21 @@ def take_from_idx(data: List[float],
List[int]: indices of element matching predicate left List[int]: indices of element matching predicate left
and right from index and right from index
""" """
lower = list(reversed( lower = list(
[i for i, _ in reversed(
it.takewhile(predicate, [
reversed(list(enumerate(data[:idx]))))])) i
upper = [i for i, _ in for i, _ in it.takewhile(
it.takewhile(predicate, predicate, reversed(list(enumerate(data[:idx])))
enumerate(data[idx:], idx))] )
]
)
)
upper = [i for i, _ in it.takewhile(predicate, enumerate(data[idx:], idx))]
return lower + upper return lower + upper
def center_from_idx(gains: List[float], def center_from_idx(gains: List[float], idx: int, delta: float = 3.0) -> int:
idx: int, delta: float = 3.0) -> int:
"""find maximum from index postion of gains in a attn dB gain span """find maximum from index postion of gains in a attn dB gain span
Args: 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) int: position of highest gain from start in range (-1 if no data)
""" """
peak_db = gains[idx] peak_db = gains[idx]
rng = take_from_idx(gains, idx, rng = take_from_idx(gains, idx, lambda i: abs(peak_db - i[1]) < delta)
lambda i: abs(peak_db - i[1]) < delta)
return max(rng, key=lambda i: gains[i]) if rng else -1 return max(rng, key=lambda i: gains[i]) if rng else -1
def cut_off_left(gains: List[float], idx: int, def cut_off_left(
peak_gain: float, attn: float = 3.0) -> int: 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 """find first position in list where gain in attn lower then peak
left from index 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) int: position of attenuation point. (-1 if no data)
""" """
return next( return next(
(i for i in range(idx, -1, -1) if (i for i in range(idx, -1, -1) if (peak_gain - gains[i]) > attn), -1
(peak_gain - gains[i]) > attn), )
-1)
def cut_off_right(gains: List[float], idx: int, def cut_off_right(
peak_gain: float, attn: float = 3.0) -> int: 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 """find first position in list where gain in attn lower then peak
right from index right from index
@ -165,19 +166,20 @@ def cut_off_right(gains: List[float], idx: int,
""" """
return next( return next(
(i for i in range(idx, len(gains)) if (i for i in range(idx, len(gains)) if (peak_gain - gains[i]) > attn), -1
(peak_gain - gains[i]) > attn), )
-1)
def dip_cut_offs(gains: List[float], peak_gain: float, def dip_cut_offs(
attn: float = 3.0) -> Tuple[int, int]: gains: List[float], peak_gain: float, attn: float = 3.0
) -> Tuple[int, int]:
rng = np.where(np.array(gains) < (peak_gain - attn))[0].tolist() rng = np.where(np.array(gains) < (peak_gain - attn))[0].tolist()
return (rng[0], rng[-1]) if rng else (math.nan, math.nan) return (rng[0], rng[-1]) if rng else (math.nan, math.nan)
def calculate_rolloff(s21: List[Datapoint], def calculate_rolloff(
idx_1: int, idx_2: int) -> Tuple[float, float]: s21: List[Datapoint], idx_1: int, idx_2: int
) -> Tuple[float, float]:
if idx_1 == idx_2: if idx_1 == idx_2:
return (math.nan, math.nan) return (math.nan, math.nan)
freq_1, freq_2 = s21[idx_1].freq, s21[idx_2].freq freq_1, freq_2 = s21[idx_1].freq, s21[idx_2].freq

Wyświetl plik

@ -35,7 +35,8 @@ IDEAL_OPEN = complex(1, 0)
IDEAL_LOAD = complex(0, 0) IDEAL_LOAD = complex(0, 0)
IDEAL_THROUGH = complex(1, 0) IDEAL_THROUGH = complex(1, 0)
RXP_CAL_HEADER = re.compile(r""" RXP_CAL_HEADER = re.compile(
r"""
^ \# \s+ Hz \s+ ^ \# \s+ Hz \s+
ShortR \s+ ShortI \s+ OpenR \s+ OpenI \s+ ShortR \s+ ShortI \s+ OpenR \s+ OpenI \s+
LoadR \s+ LoadI LoadR \s+ LoadI
@ -43,9 +44,12 @@ RXP_CAL_HEADER = re.compile(r"""
(?P<thrurefl> \s+ ThrureflR \s+ ThrureflI)? (?P<thrurefl> \s+ ThrureflR \s+ ThrureflI)?
(?P<isolation> \s+ IsolationR \s+ IsolationI)? (?P<isolation> \s+ IsolationR \s+ IsolationI)?
\s* $ \s* $
""", re.VERBOSE | re.IGNORECASE) """,
re.VERBOSE | re.IGNORECASE,
)
RXP_CAL_LINE = re.compile(r""" RXP_CAL_LINE = re.compile(
r"""
^ \s* ^ \s*
(?P<freq>\d+) \s+ (?P<freq>\d+) \s+
(?P<shortr>[-0-9Ee.]+) \s+ (?P<shorti>[-0-9Ee.]+) \s+ (?P<shortr>[-0-9Ee.]+) \s+ (?P<shorti>[-0-9Ee.]+) \s+
@ -55,7 +59,9 @@ RXP_CAL_LINE = re.compile(r"""
( \s+ (?P<thrureflr>[-0-9Ee.]+) \s+ (?P<thrurefli>[-0-9Ee.]+))? ( \s+ (?P<thrureflr>[-0-9Ee.]+) \s+ (?P<thrurefli>[-0-9Ee.]+))?
( \s+ (?P<isolationr>[-0-9Ee.]+) \s+ (?P<isolationi>[-0-9Ee.]+))? ( \s+ (?P<isolationr>[-0-9Ee.]+) \s+ (?P<isolationi>[-0-9Ee.]+))?
\s* $ \s* $
""", re.VERBOSE) """,
re.VERBOSE,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -63,7 +69,8 @@ logger = logging.getLogger(__name__)
def correct_delay(d: Datapoint, delay: float, reflect: bool = False): def correct_delay(d: Datapoint, delay: float, reflect: bool = False):
mult = 2 if reflect else 1 mult = 2 if reflect else 1
corr_data = d.z * cmath.exp( 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) return Datapoint(d.freq, corr_data.real, corr_data.imag)
@ -88,14 +95,16 @@ class CalData:
def __str__(self): def __str__(self):
return ( return (
f'{self.freq}' f"{self.freq}"
f' {self.short.real} {self.short.imag}' f" {self.short.real} {self.short.imag}"
f' {self.open.real} {self.open.imag}' f" {self.open.real} {self.open.imag}"
f' {self.load.real} {self.load.imag}' + ( f" {self.load.real} {self.load.imag}"
f' {self.through.real} {self.through.imag}' + (
f' {self.thrurefl.real} {self.thrurefl.imag}' f" {self.through.real} {self.through.imag}"
f' {self.isolation.real} {self.isolation.imag}' f" {self.thrurefl.real} {self.thrurefl.imag}"
if self.through else '' f" {self.isolation.real} {self.isolation.imag}"
if self.through
else ""
) )
) )
@ -138,26 +147,32 @@ class CalDataSet(UserDict):
( (
"# Calibration data for NanoVNA-Saver\n" "# Calibration data for NanoVNA-Saver\n"
+ "\n".join([f"! {note}" for note in self.notes.splitlines()]) + "\n".join([f"! {note}" for note in self.notes.splitlines()])
+ "\n" + "# Hz ShortR ShortI OpenR OpenI LoadR LoadI" + "\n"
+ (" ThroughR ThroughI ThrureflR" + "# Hz ShortR ShortI OpenR OpenI LoadR LoadI"
" ThrureflI IsolationR IsolationI\n" + (
if self.complete2port() else "\n") " ThroughR ThroughI ThrureflR"
+ "\n".join([ " ThrureflI IsolationR IsolationI\n"
f"{self.data.get(freq)}" for freq in self.frequencies() if self.complete2port()
]) + "\n" 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, def _append_match(
line_nr: int, line: str) -> None: self, m: re.Match, header: str, line_nr: int, line: str
) -> None:
cal = m.groupdict() cal = m.groupdict()
columns = { columns = {col[:-1] for col in cal.keys() if cal[col] and col != "freq"}
col[:-1] for col in cal.keys() if cal[col] and col != "freq"
}
if "through" in columns and header == "sol": if "through" in columns and header == "sol":
logger.warning("Through data with sol header. %i: %s", logger.warning(
line_nr, line) "Through data with sol header. %i: %s", line_nr, line
)
# fix short data (without thrurefl) # fix short data (without thrurefl)
if "thrurefl" in columns and "isolation" not in columns: if "thrurefl" in columns and "isolation" not in columns:
cal["isolationr"] = cal["thrureflr"] cal["isolationr"] = cal["thrureflr"]
@ -166,11 +181,14 @@ class CalDataSet(UserDict):
for name in columns: for name in columns:
self.insert( self.insert(
name, name,
Datapoint(int(cal["freq"]), Datapoint(
float(cal[f"{name}r"]), int(cal["freq"]),
float(cal[f"{name}i"]))) 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 # reset data
self.notes = "" self.notes = ""
self.data = defaultdict(CalData) self.data = defaultdict(CalData)
@ -185,7 +203,8 @@ class CalDataSet(UserDict):
if m := RXP_CAL_HEADER.search(line): if m := RXP_CAL_HEADER.search(line):
if header: if header:
logger.warning( 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" header = "through" if m.group("through") else "sol"
continue continue
if not line or line.startswith("#"): if not line or line.startswith("#"):
@ -197,13 +216,20 @@ class CalDataSet(UserDict):
continue continue
if not header: if not header:
logger.warning( 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) self._append_match(m, header, line, i)
return self return self
def insert(self, name: str, dp: Datapoint): def insert(self, name: str, dp: Datapoint):
if name not in {'short', 'open', 'load', if name not in {
'through', 'thrurefl', 'isolation'}: "short",
"open",
"load",
"through",
"thrurefl",
"isolation",
}:
raise KeyError(name) raise KeyError(name)
freq = dp.freq freq = dp.freq
setattr(self.data[freq], name, (dp.z)) setattr(self.data[freq], name, (dp.z))
@ -223,9 +249,7 @@ class CalDataSet(UserDict):
yield self.get(freq) yield self.get(freq)
def size_of(self, name: str) -> int: def size_of(self, name: str) -> int:
return len( return len([True for val in self.data.values() if getattr(val, name)])
[True for val in self.data.values() if getattr(val, name)]
)
def complete1port(self) -> bool: def complete1port(self) -> bool:
for val in self.data.values(): for val in self.data.values():
@ -244,7 +268,6 @@ class CalDataSet(UserDict):
class Calibration: class Calibration:
def __init__(self): def __init__(self):
self.notes = [] self.notes = []
self.dataset = CalDataSet() self.dataset = CalDataSet()
self.cal_element = CalElement() self.cal_element = CalElement()
@ -278,18 +301,30 @@ class Calibration:
gm2 = cal.open gm2 = cal.open
gm3 = cal.load gm3 = cal.load
denominator = (g1 * (g2 - g3) * gm1 + denominator = (
g2 * g3 * gm2 - g2 * g3 * gm3 - g1 * (g2 - g3) * gm1
(g2 * gm2 - g3 * gm3) * g1) + g2 * g3 * gm2
cal.e00 = - ((g2 * gm3 - g3 * gm3) * g1 * gm2 - - g2 * g3 * gm3
(g2 * g3 * gm2 - g2 * g3 * gm3 - - (g2 * gm2 - g3 * gm3) * g1
(g3 * gm2 - g2 * gm3) * g1) * gm1 )
) / denominator cal.e00 = (
cal.e11 = ((g2 - g3) * gm1 - g1 * (gm2 - gm3) + -(
g3 * gm2 - g2 * gm3) / denominator (g2 * gm3 - g3 * gm3) * g1 * gm2
cal.delta_e = - ((g1 * (gm2 - gm3) - g2 * gm2 + g3 * - (g2 * g3 * gm2 - g2 * g3 * gm3 - (g3 * gm2 - g2 * gm3) * g1)
gm3) * gm1 + (g2 * gm3 - g3 * gm3) * * gm1
gm2) / denominator )
/ 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): def _calc_port_2(self, freq: int, cal: CalData):
gt = self.gamma_through(freq) gt = self.gamma_through(freq)
@ -301,18 +336,16 @@ class Calibration:
cal.e30 = cal.isolation cal.e30 = cal.isolation
cal.e10e01 = cal.e00 * cal.e11 - cal.delta_e cal.e10e01 = cal.e00 * cal.e11 - cal.delta_e
cal.e22 = gm7 / ( cal.e22 = gm7 / (gm7 * cal.e11 * gt**2 + cal.e10e01 * gt**2)
gm7 * cal.e11 * gt ** 2 + cal.e10e01 * gt ** 2) cal.e10e32 = (gm4 - gm6) * (1 - cal.e11 * cal.e22 * gt**2) / gt
cal.e10e32 = (gm4 - gm6) * (
1 - cal.e11 * cal.e22 * gt ** 2) / gt
def calc_corrections(self): def calc_corrections(self):
if not self.isValid1Port(): if not self.isValid1Port():
logger.warning( logger.warning("Tried to calibrate from insufficient data.")
"Tried to calibrate from insufficient data.")
raise ValueError( raise ValueError(
"All of short, open and load calibration steps" "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()) logger.debug("Calculating calibration for %d points.", self.size())
for freq, caldata in self.dataset.items(): for freq, caldata in self.dataset.items():
@ -324,10 +357,12 @@ class Calibration:
self.isCalculated = False self.isCalculated = False
logger.error( logger.error(
"Division error - did you use the same measurement" "Division error - did you use the same measurement"
" for two of short, open and load?") " for two of short, open and load?"
)
raise ValueError( raise ValueError(
f"Two of short, open and load returned the same" 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.gen_interpolation()
self.isCalculated = True self.isCalculated = True
@ -338,25 +373,47 @@ class Calibration:
return IDEAL_SHORT return IDEAL_SHORT
logger.debug("Using short calibration set values.") logger.debug("Using short calibration set values.")
cal_element = self.cal_element cal_element = self.cal_element
Zsp = complex(0.0, 2.0 * math.pi * freq * ( Zsp = complex(
cal_element.short_l0 + cal_element.short_l1 * freq + 0.0,
cal_element.short_l2 * freq**2 + cal_element.short_l3 * freq**3)) 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) # Referencing https://arxiv.org/pdf/1606.02446.pdf (18) - (21)
return (Zsp / 50.0 - 1.0) / (Zsp / 50.0 + 1.0) * cmath.exp( return (
complex(0.0, (Zsp / 50.0 - 1.0)
-4.0 * math.pi * freq * cal_element.short_length)) / (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: def gamma_open(self, freq: int) -> complex:
if self.cal_element.open_is_ideal: if self.cal_element.open_is_ideal:
return IDEAL_OPEN return IDEAL_OPEN
logger.debug("Using open calibration set values.") logger.debug("Using open calibration set values.")
cal_element = self.cal_element cal_element = self.cal_element
Zop = complex(0.0, 2.0 * math.pi * freq * ( Zop = complex(
cal_element.open_c0 + cal_element.open_c1 * freq + 0.0,
cal_element.open_c2 * freq**2 + cal_element.open_c3 * freq**3)) 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( return ((1.0 - 50.0 * Zop) / (1.0 + 50.0 * Zop)) * cmath.exp(
complex(0.0, complex(0.0, -4.0 * math.pi * freq * cal_element.open_length)
-4.0 * math.pi * freq * cal_element.open_length)) )
def gamma_load(self, freq: int) -> complex: def gamma_load(self, freq: int) -> complex:
if self.cal_element.load_is_ideal: if self.cal_element.load_is_ideal:
@ -367,11 +424,17 @@ class Calibration:
if cal_element.load_c > 0.0: if cal_element.load_c > 0.0:
Zl = cal_element.load_r / complex( Zl = cal_element.load_r / complex(
1.0, 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: if cal_element.load_l > 0.0:
Zl = Zl + complex(0.0, 2 * math.pi * freq * cal_element.load_l) 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( return (
complex(0.0, -4 * math.pi * freq * cal_element.load_length)) (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: def gamma_through(self, freq: int) -> complex:
if self.cal_element.through_is_ideal: if self.cal_element.through_is_ideal:
@ -379,59 +442,103 @@ class Calibration:
logger.debug("Using through calibration set values.") logger.debug("Using through calibration set values.")
cal_element = self.cal_element cal_element = self.cal_element
return cmath.exp( 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): def gen_interpolation(self):
(freq, e00, e11, delta_e, e10e01, e30, e22, e10e32) = zip(*[ (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()]) (
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 = { self.interp = {
"e00": interp1d(freq, e00, "e00": interp1d(
kind="slinear", bounds_error=False, freq,
fill_value=(e00[0], e00[-1])), e00,
"e11": interp1d(freq, e11, kind="slinear",
kind="slinear", bounds_error=False, bounds_error=False,
fill_value=(e11[0], e11[-1])), fill_value=(e00[0], e00[-1]),
"delta_e": interp1d(freq, delta_e, ),
kind="slinear", bounds_error=False, "e11": interp1d(
fill_value=(delta_e[0], delta_e[-1])), freq,
"e10e01": interp1d(freq, e10e01, e11,
kind="slinear", bounds_error=False, kind="slinear",
fill_value=(e10e01[0], e10e01[-1])), bounds_error=False,
"e30": interp1d(freq, e30, fill_value=(e11[0], e11[-1]),
kind="slinear", bounds_error=False, ),
fill_value=(e30[0], e30[-1])), "delta_e": interp1d(
"e22": interp1d(freq, e22, freq,
kind="slinear", bounds_error=False, delta_e,
fill_value=(e22[0], e22[-1])), kind="slinear",
"e10e32": interp1d(freq, e10e32, bounds_error=False,
kind="slinear", bounds_error=False, fill_value=(delta_e[0], delta_e[-1]),
fill_value=(e10e32[0], e10e32[-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): def correct11(self, dp: Datapoint):
i = self.interp i = self.interp
s11 = (dp.z - i["e00"](dp.freq)) / ( 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) return Datapoint(dp.freq, s11.real, s11.imag)
def correct21(self, dp: Datapoint, dp11: Datapoint): def correct21(self, dp: Datapoint, dp11: Datapoint):
i = self.interp i = self.interp
s21 = (dp.z - i["e30"](dp.freq)) / i["e10e32"](dp.freq) s21 = (dp.z - i["e30"](dp.freq)) / i["e10e32"](dp.freq)
s21 = s21 * (i["e10e01"](dp.freq) / (i["e11"](dp.freq) s21 = s21 * (
* dp11.z - i["delta_e"](dp.freq))) i["e10e01"](dp.freq)
/ (i["e11"](dp.freq) * dp11.z - i["delta_e"](dp.freq))
)
return Datapoint(dp.freq, s21.real, s21.imag) return Datapoint(dp.freq, s21.real, s21.imag)
def save(self, filename: str): def save(self, filename: str):
self.dataset.notes = "\n".join(self.notes) self.dataset.notes = "\n".join(self.notes)
if not self.isValid1Port(): if not self.isValid1Port():
raise ValueError("Not a valid calibration") 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)) calfile.write(str(self.dataset))
def load(self, filename): def load(self, filename):
self.source = os.path.basename(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.dataset = CalDataSet().from_str(calfile.read())
self.notes = self.dataset.notes.splitlines() self.notes = self.dataset.notes.splitlines()

Wyświetl plik

@ -61,20 +61,24 @@ class CombinedLogMagChart(LogMagChart):
def drawChart(self, qp: QtGui.QPainter): def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
qp.drawText(int(self.dim.width // 2) - 20, qp.drawText(
15, int(self.dim.width // 2) - 20, 15, f"{self.name} {self.name_unit}"
f"{self.name} {self.name_unit}") )
qp.drawText(10, 15, "S11") qp.drawText(10, 15, "S11")
qp.drawText(self.leftMargin + self.dim.width - 8, 15, "S21") qp.drawText(self.leftMargin + self.dim.width - 8, 15, "S21")
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin, qp.drawLine(
self.topMargin - 5, self.leftMargin,
self.leftMargin, self.topMargin - 5,
self.topMargin + self.dim.height + 5) self.leftMargin,
qp.drawLine(self.leftMargin - 5, self.topMargin + self.dim.height + 5,
self.topMargin + self.dim.height, )
self.leftMargin + self.dim.width, qp.drawLine(
self.topMargin + self.dim.height) self.leftMargin - 5,
self.topMargin + self.dim.height,
self.leftMargin + self.dim.width,
self.topMargin + self.dim.height,
)
def drawValues(self, qp: QtGui.QPainter): def drawValues(self, qp: QtGui.QPainter):
if len(self.data11) == 0 and len(self.reference11) == 0: if len(self.data11) == 0 and len(self.reference11) == 0:
@ -117,8 +121,12 @@ class CombinedLogMagChart(LogMagChart):
pen = QtGui.QPen(c) pen = QtGui.QPen(c)
pen.setWidth(2) pen.setWidth(2)
qp.setPen(pen) qp.setPen(pen)
qp.drawLine(self.leftMargin + self.dim.width - 20, 9, qp.drawLine(
self.leftMargin + self.dim.width - 15, 9) self.leftMargin + self.dim.width - 20,
9,
self.leftMargin + self.dim.width - 15,
9,
)
if self.reference11: if self.reference11:
c = QtGui.QColor(Chart.color.reference) c = QtGui.QColor(Chart.color.reference)
@ -132,8 +140,12 @@ class CombinedLogMagChart(LogMagChart):
pen = QtGui.QPen(c) pen = QtGui.QPen(c)
pen.setWidth(2) pen.setWidth(2)
qp.setPen(pen) qp.setPen(pen)
qp.drawLine(self.leftMargin + self.dim.width - 20, 14, qp.drawLine(
self.leftMargin + self.dim.width - 15, 14) 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.data11, Chart.color.sweep)
self.drawData(qp, self.data21, Chart.color.sweep_secondary) self.drawData(qp, self.data21, Chart.color.sweep_secondary)

Wyświetl plik

@ -36,13 +36,16 @@ logger = logging.getLogger(__name__)
class ChartColors: # pylint: disable=too-many-instance-attributes class ChartColors: # pylint: disable=too-many-instance-attributes
background: QColor = field(default_factory=lambda: QColor(QtCore.Qt.white)) background: QColor = field(default_factory=lambda: QColor(QtCore.Qt.white))
foreground: QColor = field( 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: QColor = field(default_factory=lambda: QColor(0, 0, 255, 64))
reference_secondary: QColor = field( 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: QColor = field(default_factory=lambda: QColor(QtCore.Qt.darkYellow))
sweep_secondary: QColor = field( 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)) swr: QColor = field(default_factory=lambda: QColor(255, 0, 0, 128))
text: QColor = field(default_factory=lambda: QColor(QtCore.Qt.black)) text: QColor = field(default_factory=lambda: QColor(QtCore.Qt.black))
bands: QColor = field(default_factory=lambda: QColor(128, 128, 128, 48)) 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: if text and Defaults.cfg.chart.marker_label:
text_width = self.qp.fontMetrics().horizontalAdvance(text) text_width = self.qp.fontMetrics().horizontalAdvance(text)
self.qp.drawText(x - int(text_width // 2), self.qp.drawText(x - int(text_width // 2), y - 3 - offset, text)
y - 3 - offset, text)
class Chart(QtWidgets.QWidget): class Chart(QtWidgets.QWidget):
@ -109,7 +111,7 @@ class Chart(QtWidgets.QWidget):
def __init__(self, name): def __init__(self, name):
super().__init__() super().__init__()
self.name = name self.name = name
self.sweepTitle = '' self.sweepTitle = ""
self.leftMargin = 30 self.leftMargin = 30
self.rightMargin = 20 self.rightMargin = 20
@ -130,7 +132,8 @@ class Chart(QtWidgets.QWidget):
self.action_popout = QtWidgets.QAction("Popout chart") self.action_popout = QtWidgets.QAction("Popout chart")
self.action_popout.triggered.connect( self.action_popout.triggered.connect(
lambda: self.popoutRequested.emit(self)) lambda: self.popoutRequested.emit(self)
)
self.addAction(self.action_popout) self.addAction(self.action_popout)
self.action_save_screenshot = QtWidgets.QAction("Save image") self.action_save_screenshot = QtWidgets.QAction("Save image")
@ -230,7 +233,9 @@ class Chart(QtWidgets.QWidget):
self.zoomTo( self.zoomTo(
self.dragbox.pos_start[0], self.dragbox.pos_start[0],
self.dragbox.pos_start[1], self.dragbox.pos_start[1],
a0.x(), a0.y()) a0.x(),
a0.y(),
)
self.dragbox.state = False self.dragbox.state = False
self.dragbox.pos = (-1, -1) self.dragbox.pos = (-1, -1)
self.dragbox.pos_start = (0, 0) self.dragbox.pos_start = (0, 0)
@ -262,7 +267,7 @@ class Chart(QtWidgets.QWidget):
int(self.leftMargin + ratio_x * factor_x), int(self.leftMargin + ratio_x * factor_x),
int(self.topMargin + ratio_y * factor_y), int(self.topMargin + ratio_y * factor_y),
int(self.leftMargin + self.dim.width - (1 - ratio_x) * factor_x), 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() a0.accept()
@ -272,8 +277,10 @@ class Chart(QtWidgets.QWidget):
def saveScreenshot(self): def saveScreenshot(self):
logger.info("Saving %s to file...", self.name) logger.info("Saving %s to file...", self.name)
filename, _ = QtWidgets.QFileDialog.getSaveFileName( filename, _ = QtWidgets.QFileDialog.getSaveFileName(
parent=self, caption="Save image", parent=self,
filter="PNG (*.png);;All files (*.*)") caption="Save image",
filter="PNG (*.png);;All files (*.*)",
)
logger.debug("Filename: %s", filename) logger.debug("Filename: %s", filename)
if not filename: if not filename:
@ -314,9 +321,9 @@ class Chart(QtWidgets.QWidget):
self.update() self.update()
@staticmethod @staticmethod
def drawMarker(x: int, y: int, def drawMarker(
qp: QtGui.QPainter, color: QtGui.QColor, x: int, y: int, qp: QtGui.QPainter, color: QtGui.QColor, number: int = 0
number: int = 0): ):
cmarker = ChartMarker(qp) cmarker = ChartMarker(qp)
cmarker.draw(x, y, color, f"{number}") cmarker.draw(x, y, color, f"{number}")

Wyświetl plik

@ -25,9 +25,12 @@ from PyQt5 import QtWidgets, QtGui, QtCore
from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Charts.Chart import Chart
from NanoVNASaver.Formatting import ( from NanoVNASaver.Formatting import (
parse_frequency, parse_value, parse_frequency,
format_frequency_chart, format_frequency_chart_2, parse_value,
format_y_axis) format_frequency_chart,
format_frequency_chart_2,
format_y_axis,
)
from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.RFTools import Datapoint
from NanoVNASaver.SITools import Format, Value from NanoVNASaver.SITools import Format, Value
@ -35,7 +38,6 @@ logger = logging.getLogger(__name__)
class FrequencyChart(Chart): class FrequencyChart(Chart):
def __init__(self, name): def __init__(self, name):
super().__init__(name) super().__init__(name)
self.maxFrequency = 100000000 self.maxFrequency = 100000000
@ -79,11 +81,13 @@ class FrequencyChart(Chart):
self.action_automatic.setCheckable(True) self.action_automatic.setCheckable(True)
self.action_automatic.setChecked(True) self.action_automatic.setChecked(True)
self.action_automatic.changed.connect( 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 = QtWidgets.QAction("Fixed span")
self.action_fixed_span.setCheckable(True) self.action_fixed_span.setCheckable(True)
self.action_fixed_span.changed.connect( 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_automatic)
mode_group.addAction(self.action_fixed_span) mode_group.addAction(self.action_fixed_span)
self.x_menu.addAction(self.action_automatic) self.x_menu.addAction(self.action_automatic)
@ -91,11 +95,13 @@ class FrequencyChart(Chart):
self.x_menu.addSeparator() self.x_menu.addSeparator()
self.action_set_fixed_start = QtWidgets.QAction( 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_start.triggered.connect(self.setMinimumFrequency)
self.action_set_fixed_stop = QtWidgets.QAction( 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.action_set_fixed_stop.triggered.connect(self.setMaximumFrequency)
self.x_menu.addAction(self.action_set_fixed_start) 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_linear_x)
frequency_mode_group.addAction(self.action_set_logarithmic_x) frequency_mode_group.addAction(self.action_set_logarithmic_x)
self.action_set_linear_x.triggered.connect( self.action_set_linear_x.triggered.connect(
lambda: self.setLogarithmicX(False)) lambda: self.setLogarithmicX(False)
)
self.action_set_logarithmic_x.triggered.connect( self.action_set_logarithmic_x.triggered.connect(
lambda: self.setLogarithmicX(True)) lambda: self.setLogarithmicX(True)
)
self.action_set_linear_x.setChecked(True) self.action_set_linear_x.setChecked(True)
self.x_menu.addAction(self.action_set_linear_x) self.x_menu.addAction(self.action_set_linear_x)
self.x_menu.addAction(self.action_set_logarithmic_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.setCheckable(True)
self.y_action_automatic.setChecked(True) self.y_action_automatic.setChecked(True)
self.y_action_automatic.changed.connect( 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 = QtWidgets.QAction("Fixed span")
self.y_action_fixed_span.setCheckable(True) self.y_action_fixed_span.setCheckable(True)
self.y_action_fixed_span.changed.connect( 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 = QtWidgets.QActionGroup(self)
mode_group.addAction(self.y_action_automatic) mode_group.addAction(self.y_action_automatic)
mode_group.addAction(self.y_action_fixed_span) mode_group.addAction(self.y_action_fixed_span)
@ -135,11 +145,13 @@ class FrequencyChart(Chart):
self.y_menu.addSeparator() self.y_menu.addSeparator()
self.action_set_fixed_minimum = QtWidgets.QAction( 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_minimum.triggered.connect(self.setMinimumValue)
self.action_set_fixed_maximum = QtWidgets.QAction( 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.action_set_fixed_maximum.triggered.connect(self.setMaximumValue)
self.y_menu.addAction(self.action_set_fixed_maximum) 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_linear_y)
vertical_mode_group.addAction(self.action_set_logarithmic_y) vertical_mode_group.addAction(self.action_set_logarithmic_y)
self.action_set_linear_y.triggered.connect( self.action_set_linear_y.triggered.connect(
lambda: self.setLogarithmicY(False)) lambda: self.setLogarithmicY(False)
)
self.action_set_logarithmic_y.triggered.connect( self.action_set_logarithmic_y.triggered.connect(
lambda: self.setLogarithmicY(True)) lambda: self.setLogarithmicY(True)
)
self.action_set_linear_y.setChecked(True) self.action_set_linear_y.setChecked(True)
self.y_menu.addAction(self.action_set_linear_y) self.y_menu.addAction(self.action_set_linear_y)
self.y_menu.addAction(self.action_set_logarithmic_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.menu.addAction(self.action_save_screenshot)
self.action_popout = QtWidgets.QAction("Popout chart") self.action_popout = QtWidgets.QAction("Popout chart")
self.action_popout.triggered.connect( self.action_popout.triggered.connect(
lambda: self.popoutRequested.emit(self)) lambda: self.popoutRequested.emit(self)
)
self.menu.addAction(self.action_popout) self.menu.addAction(self.action_popout)
self.setFocusPolicy(QtCore.Qt.ClickFocus) self.setFocusPolicy(QtCore.Qt.ClickFocus)
self.setMinimumSize( self.setMinimumSize(
self.dim.width + self.rightMargin + self.leftMargin, self.dim.width + self.rightMargin + self.leftMargin,
self.dim.height + self.topMargin + self.bottomMargin) self.dim.height + self.topMargin + self.bottomMargin,
)
self.setSizePolicy( self.setSizePolicy(
QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.MinimumExpanding)) QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding,
)
)
pal = QtGui.QPalette() pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, Chart.color.background) pal.setColor(QtGui.QPalette.Background, Chart.color.background)
self.setPalette(pal) self.setPalette(pal)
@ -197,13 +216,17 @@ class FrequencyChart(Chart):
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
self.action_set_fixed_start.setText( 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( 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( self.action_set_fixed_minimum.setText(
f"Minimum ({self.minDisplayValue})") f"Minimum ({self.minDisplayValue})"
)
self.action_set_fixed_maximum.setText( self.action_set_fixed_maximum.setText(
f"Maximum ({self.maxDisplayValue})") f"Maximum ({self.maxDisplayValue})"
)
if self.fixedSpan: if self.fixedSpan:
self.action_fixed_span.setChecked(True) self.action_fixed_span.setChecked(True)
@ -242,8 +265,11 @@ class FrequencyChart(Chart):
def setMinimumFrequency(self): def setMinimumFrequency(self):
min_freq_str, selected = QtWidgets.QInputDialog.getText( min_freq_str, selected = QtWidgets.QInputDialog.getText(
self, "Start frequency", self,
"Set start frequency", text=str(self.minFrequency)) "Start frequency",
"Set start frequency",
text=str(self.minFrequency),
)
if not selected: if not selected:
return return
span = abs(self.maxFrequency - self.minFrequency) span = abs(self.maxFrequency - self.minFrequency)
@ -258,8 +284,11 @@ class FrequencyChart(Chart):
def setMaximumFrequency(self): def setMaximumFrequency(self):
max_freq_str, selected = QtWidgets.QInputDialog.getText( max_freq_str, selected = QtWidgets.QInputDialog.getText(
self, "Stop frequency", self,
"Set stop frequency", text=str(self.maxFrequency)) "Stop frequency",
"Set stop frequency",
text=str(self.maxFrequency),
)
if not selected: if not selected:
return return
span = abs(self.maxFrequency - self.minFrequency) span = abs(self.maxFrequency - self.minFrequency)
@ -274,9 +303,11 @@ class FrequencyChart(Chart):
def setMinimumValue(self): def setMinimumValue(self):
text, selected = QtWidgets.QInputDialog.getText( text, selected = QtWidgets.QInputDialog.getText(
self, "Minimum value", self,
"Minimum value",
"Set 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: if not selected:
return return
min_val = parse_value(text) min_val = parse_value(text)
@ -292,9 +323,11 @@ class FrequencyChart(Chart):
def setMaximumValue(self): def setMaximumValue(self):
text, selected = QtWidgets.QInputDialog.getText( text, selected = QtWidgets.QInputDialog.getText(
self, "Maximum value", self,
"Maximum value",
"Set 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: if not selected:
return return
max_val = parse_value(text) max_val = parse_value(text)
@ -323,18 +356,21 @@ class FrequencyChart(Chart):
if self.logarithmicX: if self.logarithmicX:
span = math.log(self.fstop) - math.log(self.fstart) span = math.log(self.fstop) - math.log(self.fstart)
return self.leftMargin + round( return self.leftMargin + round(
self.dim.width * (math.log(d.freq) - self.dim.width
math.log(self.fstart)) / span) * (math.log(d.freq) - math.log(self.fstart))
/ span
)
return self.leftMargin + round( 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) return math.floor(self.width() / 2)
def getYPosition(self, d: Datapoint) -> int: def getYPosition(self, d: Datapoint) -> int:
try: try:
return ( return self.topMargin + round(
self.topMargin + round( (self.maxValue - self.value_function(d))
(self.maxValue - self.value_function(d)) / / self.span
self.span * self.dim.height) * self.dim.height
) )
except ValueError: except ValueError:
return self.topMargin return self.topMargin
@ -410,9 +446,12 @@ class FrequencyChart(Chart):
if self.dragbox.move_x != -1 and self.dragbox.move_y != -1: if self.dragbox.move_x != -1 and self.dragbox.move_y != -1:
dx = self.dragbox.move_x - a0.x() dx = self.dragbox.move_x - a0.x()
dy = self.dragbox.move_y - a0.y() dy = self.dragbox.move_y - a0.y()
self.zoomTo(self.leftMargin + dx, self.topMargin + dy, self.zoomTo(
self.leftMargin + self.dim.width + dx, self.leftMargin + dx,
self.topMargin + self.dim.height + dy) self.topMargin + dy,
self.leftMargin + self.dim.width + dx,
self.topMargin + self.dim.height + dy,
)
self.dragbox.move_x = a0.x() self.dragbox.move_x = a0.x()
self.dragbox.move_y = a0.y() self.dragbox.move_y = a0.y()
@ -436,10 +475,10 @@ class FrequencyChart(Chart):
m.setFrequency(str(f)) m.setFrequency(str(f))
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None: def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
self.dim.width = ( self.dim.width = a0.size().width() - self.rightMargin - self.leftMargin
a0.size().width() - self.rightMargin - self.leftMargin)
self.dim.height = ( self.dim.height = (
a0.size().height() - self.bottomMargin - self.topMargin) a0.size().height() - self.bottomMargin - self.topMargin
)
self.update() self.update()
def paintEvent(self, _: QtGui.QPaintEvent) -> None: def paintEvent(self, _: QtGui.QPaintEvent) -> None:
@ -452,24 +491,30 @@ class FrequencyChart(Chart):
qp.end() qp.end()
def _data_oob(self, data: List[Datapoint]) -> bool: 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): def _check_frequency_boundaries(self, qp: QtGui.QPainter):
if (self.data and self._data_oob(self.data) and if (
(not self.reference or self._data_oob(self.reference))): self.data
and self._data_oob(self.data)
and (not self.reference or self._data_oob(self.reference))
):
# Data outside frequency range # Data outside frequency range
qp.setBackgroundMode(QtCore.Qt.OpaqueMode) qp.setBackgroundMode(QtCore.Qt.OpaqueMode)
qp.setBackground(Chart.color.background) qp.setBackground(Chart.color.background)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
qp.drawText(self.leftMargin + int(self.dim.width // 2) - 70, qp.drawText(
self.topMargin + int(self.dim.height // 2) - 20, self.leftMargin + int(self.dim.width // 2) - 70,
"Data outside frequency span") self.topMargin + int(self.dim.height // 2) - 20,
"Data outside frequency span",
)
def drawDragbog(self, qp: QtGui.QPainter): def drawDragbog(self, qp: QtGui.QPainter):
dashed_pen = QtGui.QPen(Chart.color.foreground, 1, QtCore.Qt.DashLine) dashed_pen = QtGui.QPen(Chart.color.foreground, 1, QtCore.Qt.DashLine)
qp.setPen(dashed_pen) qp.setPen(dashed_pen)
top_left = QtCore.QPoint( 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]) bottom_right = QtCore.QPoint(self.dragbox.pos[0], self.dragbox.pos[1])
rect = QtCore.QRect(top_left, bottom_right) rect = QtCore.QRect(top_left, bottom_right)
qp.drawRect(rect) qp.drawRect(rect)
@ -481,14 +526,18 @@ class FrequencyChart(Chart):
headline += f" ({self.name_unit})" headline += f" ({self.name_unit})"
qp.drawText(3, 15, headline) qp.drawText(3, 15, headline)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin, qp.drawLine(
20, self.leftMargin,
self.leftMargin, 20,
self.topMargin + self.dim.height + 5) self.leftMargin,
qp.drawLine(self.leftMargin - 5, self.topMargin + self.dim.height + 5,
self.topMargin + self.dim.height, )
self.leftMargin + self.dim.width, qp.drawLine(
self.topMargin + self.dim.height) self.leftMargin - 5,
self.topMargin + self.dim.height,
self.leftMargin + self.dim.width,
self.topMargin + self.dim.height,
)
self.drawTitle(qp) self.drawTitle(qp)
def drawValues(self, qp: QtGui.QPainter): def drawValues(self, qp: QtGui.QPainter):
@ -514,7 +563,8 @@ class FrequencyChart(Chart):
if span == 0: if span == 0:
logger.info( logger.info(
"Span is zero for %s-Chart, setting to a small value.", "Span is zero for %s-Chart, setting to a small value.",
self.name) self.name,
)
span = 1e-15 span = 1e-15
self.span = span self.span = span
@ -522,23 +572,30 @@ class FrequencyChart(Chart):
fmt = Format(max_nr_digits=1) fmt = Format(max_nr_digits=1)
for i in range(target_ticks): for i in range(target_ticks):
val = min_value + (i / target_ticks) * span val = min_value + (i / target_ticks) * span
y = self.topMargin + \ y = self.topMargin + round(
round((self.maxValue - val) / self.span * self.dim.height) (self.maxValue - val) / self.span * self.dim.height
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
if val != min_value: if val != min_value:
valstr = str(Value(val, fmt=fmt)) valstr = str(Value(val, fmt=fmt))
qp.drawText(3, y + 3, valstr) qp.drawText(3, y + 3, valstr)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width, y
)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, self.topMargin, qp.drawLine(
self.leftMargin + self.dim.width, self.topMargin) self.leftMargin - 5,
self.topMargin,
self.leftMargin + self.dim.width,
self.topMargin,
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
qp.drawText(3, self.topMargin + 4, str(Value(max_value, fmt=fmt))) qp.drawText(3, self.topMargin + 4, str(Value(max_value, fmt=fmt)))
qp.drawText(3, self.dim.height + self.topMargin, qp.drawText(
str(Value(min_value, fmt=fmt))) 3, self.dim.height + self.topMargin, str(Value(min_value, fmt=fmt))
)
self.drawFrequencyTicks(qp) self.drawFrequencyTicks(qp)
self.drawData(qp, self.data, Chart.color.sweep) self.drawData(qp, self.data, Chart.color.sweep)
@ -574,27 +631,31 @@ class FrequencyChart(Chart):
else: else:
my_format_frequency = format_frequency_chart_2 my_format_frequency = format_frequency_chart_2
qp.drawText(self.leftMargin - 20, qp.drawText(
self.topMargin + self.dim.height + 15, self.leftMargin - 20,
my_format_frequency(self.fstart)) self.topMargin + self.dim.height + 15,
my_format_frequency(self.fstart),
)
for i in range(ticks): for i in range(ticks):
x = self.leftMargin + round((i + 1) * self.dim.width / ticks) x = self.leftMargin + round((i + 1) * self.dim.width / ticks)
if self.logarithmicX: if self.logarithmicX:
fspan = math.log(self.fstop) - math.log(self.fstart) fspan = math.log(self.fstop) - math.log(self.fstart)
freq = round( freq = round(
math.exp( math.exp(((i + 1) * fspan / ticks) + math.log(self.fstart))
((i + 1) * fspan / ticks) + )
math.log(self.fstart)))
else: else:
freq = round(fspan / ticks * (i + 1) + self.fstart) freq = round(fspan / ticks * (i + 1) + self.fstart)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(x, self.topMargin, x, qp.drawLine(
self.topMargin + self.dim.height + 5) x, self.topMargin, x, self.topMargin + self.dim.height + 5
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
qp.drawText(x - 20, qp.drawText(
self.topMargin + self.dim.height + 15, x - 20,
my_format_frequency(freq)) self.topMargin + self.dim.height + 15,
my_format_frequency(freq),
)
def drawBands(self, qp, fstart, fstop): def drawBands(self, qp, fstart, fstop):
qp.setBrush(self.bands.color) 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 # don't draw if either band not in chart or completely in band
if start < fstart < fstop < end or end < fstart or start > fstop: if start < fstart < fstop < end or end < fstart or start > fstop:
continue continue
x_start = max(self.leftMargin + 1, x_start = max(
self.getXPosition(Datapoint(start, 0, 0))) self.leftMargin + 1, self.getXPosition(Datapoint(start, 0, 0))
x_stop = min(self.leftMargin + self.dim.width, )
self.getXPosition(Datapoint(end, 0, 0))) x_stop = min(
qp.drawRect(x_start, self.leftMargin + self.dim.width,
self.topMargin, self.getXPosition(Datapoint(end, 0, 0)),
x_stop - x_start, )
self.dim.height) qp.drawRect(
x_start, self.topMargin, x_stop - x_start, self.dim.height
)
def drawData(self, qp: QtGui.QPainter, data: List[Datapoint], def drawData(
color: QtGui.QColor, y_function=None): self,
qp: QtGui.QPainter,
data: List[Datapoint],
color: QtGui.QColor,
y_function=None,
):
if y_function is None: if y_function is None:
y_function = self.getYPosition y_function = self.getYPosition
pen = QtGui.QPen(color) pen = QtGui.QPen(color)
@ -643,8 +711,7 @@ class FrequencyChart(Chart):
if self.isPlotable(prevx, prevy): if self.isPlotable(prevx, prevy):
qp.drawLine(x, y, prevx, prevy) qp.drawLine(x, y, prevx, prevy)
else: else:
new_x, new_y = self.getPlotable( new_x, new_y = self.getPlotable(x, y, prevx, prevy)
x, y, prevx, prevy)
qp.drawLine(x, y, new_x, new_y) qp.drawLine(x, y, new_x, new_y)
elif self.isPlotable(prevx, prevy): elif self.isPlotable(prevx, prevy):
new_x, new_y = self.getPlotable(prevx, prevy, x, y) new_x, new_y = self.getPlotable(prevx, prevy, x, y)
@ -663,13 +730,17 @@ class FrequencyChart(Chart):
x = self.getXPosition(data[m.location]) x = self.getXPosition(data[m.location])
y = y_function(data[m.location]) y = y_function(data[m.location])
if self.isPlotable(x, y): if self.isPlotable(x, y):
self.drawMarker(x, y, qp, m.color, self.drawMarker(
self.markers.index(m) + 1) x, y, qp, m.color, self.markers.index(m) + 1
)
def isPlotable(self, x, y): def isPlotable(self, x, y):
return y is not None and x is not None and \ return (
self.leftMargin <= x <= self.leftMargin + self.dim.width and \ y is not None
self.topMargin <= y <= self.topMargin + self.dim.height 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): def getPlotable(self, x, y, distantx, distanty):
p1 = np.array([x, y]) p1 = np.array([x, y])
@ -680,8 +751,12 @@ class FrequencyChart(Chart):
p4 = np.array([self.leftMargin + self.dim.width, self.topMargin]) p4 = np.array([self.leftMargin + self.dim.width, self.topMargin])
elif distanty > self.topMargin + self.dim.height: elif distanty > self.topMargin + self.dim.height:
p3 = np.array([self.leftMargin, self.topMargin + self.dim.height]) p3 = np.array([self.leftMargin, self.topMargin + self.dim.height])
p4 = np.array([self.leftMargin + self.dim.width, p4 = np.array(
self.topMargin + self.dim.height]) [
self.leftMargin + self.dim.width,
self.topMargin + self.dim.height,
]
)
else: else:
return x, y return x, y
@ -730,10 +805,14 @@ class FrequencyChart(Chart):
m = self.getActiveMarker() m = self.getActiveMarker()
if m is not None and a0.modifiers() == QtCore.Qt.NoModifier: if m is not None and a0.modifiers() == QtCore.Qt.NoModifier:
if a0.key() in [QtCore.Qt.Key_Down, QtCore.Qt.Key_Left]: if a0.key() in [QtCore.Qt.Key_Down, QtCore.Qt.Key_Left]:
m.frequencyInput.keyPressEvent(QtGui.QKeyEvent( m.frequencyInput.keyPressEvent(
a0.type(), QtCore.Qt.Key_Down, a0.modifiers())) QtGui.QKeyEvent(
a0.type(), QtCore.Qt.Key_Down, a0.modifiers()
)
)
elif a0.key() in [QtCore.Qt.Key_Up, QtCore.Qt.Key_Right]: elif a0.key() in [QtCore.Qt.Key_Up, QtCore.Qt.Key_Right]:
m.frequencyInput.keyPressEvent(QtGui.QKeyEvent( m.frequencyInput.keyPressEvent(
a0.type(), QtCore.Qt.Key_Up, a0.modifiers())) QtGui.QKeyEvent(a0.type(), QtCore.Qt.Key_Up, a0.modifiers())
)
else: else:
super().keyPressEvent(a0) super().keyPressEvent(a0)

Wyświetl plik

@ -27,6 +27,7 @@ from PyQt5 import QtGui
from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Charts.Chart import Chart
from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.RFTools import Datapoint
from .Frequency import FrequencyChart from .Frequency import FrequencyChart
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -124,23 +125,30 @@ class GroupDelayChart(FrequencyChart):
tickcount = math.floor(self.dim.height / 60) tickcount = math.floor(self.dim.height / 60)
for i in range(tickcount): for i in range(tickcount):
delay = min_delay + span * i / tickcount delay = min_delay + span * i / tickcount
y = self.topMargin + \ y = self.topMargin + round(
round((self.maxDelay - delay) / self.span * self.dim.height) (self.maxDelay - delay) / self.span * self.dim.height
)
if delay not in {min_delay, max_delay}: if delay not in {min_delay, max_delay}:
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
# TODO use format class # TODO use format class
digits = 0 if delay == 0 else max( digits = (
0, min(2, math.floor(3 - math.log10(abs(delay))))) 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)) delaystr = str(round(delay, digits if digits != 0 else None))
qp.drawText(3, y + 3, delaystr) qp.drawText(3, y + 3, delaystr)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width, y
)
qp.drawLine(self.leftMargin - 5, qp.drawLine(
self.topMargin, self.leftMargin - 5,
self.leftMargin + self.dim.width, self.topMargin,
self.topMargin) self.leftMargin + self.dim.width,
self.topMargin,
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
qp.drawText(3, self.topMargin + 5, str(max_delay)) qp.drawText(3, self.topMargin + 5, str(max_delay))
qp.drawText(3, self.dim.height + self.topMargin, str(min_delay)) qp.drawText(3, self.dim.height + self.topMargin, str(min_delay))
@ -153,15 +161,20 @@ class GroupDelayChart(FrequencyChart):
self.drawFrequencyTicks(qp) self.drawFrequencyTicks(qp)
self.draw_data(qp, Chart.color.sweep, self.draw_data(qp, Chart.color.sweep, self.data, self.groupDelay)
self.data, self.groupDelay) self.draw_data(
self.draw_data(qp, Chart.color.reference, qp, Chart.color.reference, self.reference, self.groupDelayReference
self.reference, self.groupDelayReference) )
self.drawMarkers(qp) self.drawMarkers(qp)
def draw_data(self, qp: QtGui.QPainter, color: QtGui.QColor, def draw_data(
data: List[Datapoint], delay: List[Datapoint]): self,
qp: QtGui.QPainter,
color: QtGui.QColor,
data: List[Datapoint],
delay: List[Datapoint],
):
pen = QtGui.QPen(color) pen = QtGui.QPen(color)
pen.setWidth(self.dim.point) pen.setWidth(self.dim.point)
line_pen = QtGui.QPen(color) line_pen = QtGui.QPen(color)
@ -200,7 +213,8 @@ class GroupDelayChart(FrequencyChart):
def getYPositionFromDelay(self, delay: float) -> int: def getYPositionFromDelay(self, delay: float) -> int:
return self.topMargin + 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]: def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin absy = y - self.topMargin

Wyświetl plik

@ -115,8 +115,12 @@ class LogMagChart(FrequencyChart):
self.draw_db_lines(qp, self.maxValue, self.minValue, ticks) self.draw_db_lines(qp, self.maxValue, self.minValue, ticks)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, self.topMargin, qp.drawLine(
self.leftMargin + self.dim.width, self.topMargin) self.leftMargin - 5,
self.topMargin,
self.leftMargin + self.dim.width,
self.topMargin,
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
qp.drawText(3, self.topMargin + 4, f"{self.maxValue}") qp.drawText(3, self.topMargin + 4, f"{self.maxValue}")
qp.drawText(3, self.dim.height + self.topMargin, f"{self.minValue}") qp.drawText(3, self.dim.height + self.topMargin, f"{self.minValue}")
@ -127,14 +131,17 @@ class LogMagChart(FrequencyChart):
for i in range(ticks.count): for i in range(ticks.count):
db = ticks.first + i * ticks.step db = ticks.first + i * ticks.step
y = self.topMargin + round( 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.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width, y
)
if db > minValue and db != maxValue: if db > minValue and db != maxValue:
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
qp.drawText(3, y + 4, qp.drawText(
f"{round(db, 1)}" if ticks.step < 1 else f"{db}") 3, y + 4, f"{round(db, 1)}" if ticks.step < 1 else f"{db}"
)
def draw_swr_markers(self, qp) -> None: def draw_swr_markers(self, qp) -> None:
qp.setPen(Chart.color.swr) qp.setPen(Chart.color.swr)
@ -145,9 +152,9 @@ class LogMagChart(FrequencyChart):
if self.isInverted: if self.isInverted:
logMag = logMag * -1 logMag = logMag * -1
y = self.topMargin + round( y = self.topMargin + round(
(self.maxValue - logMag) / self.span * self.dim.height) (self.maxValue - logMag) / self.span * self.dim.height
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, f"VSWR: {vswr}") qp.drawText(self.leftMargin + 3, y - 1, f"VSWR: {vswr}")
def getYPosition(self, d: Datapoint) -> int: def getYPosition(self, d: Datapoint) -> int:
@ -155,7 +162,8 @@ class LogMagChart(FrequencyChart):
if math.isinf(logMag): if math.isinf(logMag):
return self.topMargin return self.topMargin
return self.topMargin + int( 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]: def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin absy = y - self.topMargin

Wyświetl plik

@ -25,6 +25,7 @@ from PyQt5 import QtGui
from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.RFTools import Datapoint
from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Charts.Chart import Chart
from NanoVNASaver.Charts.Frequency import FrequencyChart from NanoVNASaver.Charts.Frequency import FrequencyChart
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -78,21 +79,28 @@ class MagnitudeChart(FrequencyChart):
target_ticks = int(self.dim.height // 60) target_ticks = int(self.dim.height // 60)
for i in range(target_ticks): for i in range(target_ticks):
val = min_value + i / target_ticks * self.span val = min_value + i / target_ticks * self.span
y = self.topMargin + int((self.maxValue - val) / self.span y = self.topMargin + int(
* self.dim.height) (self.maxValue - val) / self.span * self.dim.height
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
if val != min_value: if val != min_value:
digits = max(0, min(2, math.floor(3 - math.log10(abs(val))))) digits = max(0, min(2, math.floor(3 - math.log10(abs(val)))))
vswrstr = (str(round(val)) if digits == 0 else vswrstr = (
str(round(val, digits))) str(round(val)) if digits == 0 else str(round(val, digits))
)
qp.drawText(3, y + 3, vswrstr) qp.drawText(3, y + 3, vswrstr)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width, y
)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, self.topMargin, qp.drawLine(
self.leftMargin + self.dim.width, self.topMargin) self.leftMargin - 5,
self.topMargin,
self.leftMargin + self.dim.width,
self.topMargin,
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
qp.drawText(3, self.topMargin + 4, str(max_value)) qp.drawText(3, self.topMargin + 4, str(max_value))
qp.drawText(3, self.dim.height + self.topMargin, str(min_value)) qp.drawText(3, self.dim.height + self.topMargin, str(min_value))
@ -103,10 +111,10 @@ class MagnitudeChart(FrequencyChart):
if vswr <= 1: if vswr <= 1:
continue continue
mag = (vswr - 1) / (vswr + 1) mag = (vswr - 1) / (vswr + 1)
y = self.topMargin + int((self.maxValue - mag) / self.span y = self.topMargin + int(
* self.dim.height) (self.maxValue - mag) / self.span * self.dim.height
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, f"VSWR: {vswr}") qp.drawText(self.leftMargin + 3, y - 1, f"VSWR: {vswr}")
self.drawData(qp, self.data, Chart.color.sweep) self.drawData(qp, self.data, Chart.color.sweep)
@ -116,7 +124,8 @@ class MagnitudeChart(FrequencyChart):
def getYPosition(self, d: Datapoint) -> int: def getYPosition(self, d: Datapoint) -> int:
mag = self.magnitude(d) mag = self.magnitude(d)
return self.topMargin + int( 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]: def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin absy = y - self.topMargin

Wyświetl plik

@ -23,8 +23,7 @@ from typing import List
from PyQt5 import QtGui from PyQt5 import QtGui
from NanoVNASaver.RFTools import Datapoint from NanoVNASaver.RFTools import Datapoint
from NanoVNASaver.SITools import ( from NanoVNASaver.SITools import Format, Value, round_ceil, round_floor
Format, Value, round_ceil, round_floor)
from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Charts.Chart import Chart
from NanoVNASaver.Charts.Frequency import FrequencyChart from NanoVNASaver.Charts.Frequency import FrequencyChart
from NanoVNASaver.Charts.LogMag import LogMagChart from NanoVNASaver.Charts.LogMag import LogMagChart
@ -57,8 +56,10 @@ class MagnitudeZChart(FrequencyChart):
if self.fixedValues: if self.fixedValues:
self.maxValue = self.maxDisplayValue self.maxValue = self.maxDisplayValue
self.minValue = ( self.minValue = (
max(self.minDisplayValue, 0.01) if self.logarithmicY else max(self.minDisplayValue, 0.01)
self.minDisplayValue) if self.logarithmicY
else self.minDisplayValue
)
else: else:
# Find scaling # Find scaling
self.minValue = 100 self.minValue = 100
@ -92,15 +93,18 @@ class MagnitudeZChart(FrequencyChart):
for i in range(horizontal_ticks): for i in range(horizontal_ticks):
y = self.topMargin + round(i * self.dim.height / horizontal_ticks) y = self.topMargin + round(i * self.dim.height / horizontal_ticks)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width + 5, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width + 5, y
)
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
val = Value(self.valueAtPosition(y)[0], fmt=fmt) val = Value(self.valueAtPosition(y)[0], fmt=fmt)
qp.drawText(3, y + 4, str(val)) qp.drawText(3, y + 4, str(val))
qp.drawText(3, qp.drawText(
self.dim.height + self.topMargin, 3,
str(Value(self.minValue, fmt=fmt))) self.dim.height + self.topMargin,
str(Value(self.minValue, fmt=fmt)),
)
self.drawFrequencyTicks(qp) self.drawFrequencyTicks(qp)
@ -116,18 +120,22 @@ class MagnitudeZChart(FrequencyChart):
if self.logarithmicY: if self.logarithmicY:
span = math.log(self.maxValue) - math.log(self.minValue) span = math.log(self.maxValue) - math.log(self.minValue)
return self.topMargin + int( return self.topMargin + int(
(math.log(self.maxValue) - math.log(mag)) / (math.log(self.maxValue) - math.log(mag))
span * self.dim.height) / span
* self.dim.height
)
return self.topMargin + int( return self.topMargin + int(
(self.maxValue - mag) / self.span * self.dim.height) (self.maxValue - mag) / self.span * self.dim.height
)
return self.topMargin return self.topMargin
def valueAtPosition(self, y) -> List[float]: def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin absy = y - self.topMargin
if self.logarithmicY: if self.logarithmicY:
span = math.log(self.maxValue) - math.log(self.minValue) span = math.log(self.maxValue) - math.log(self.minValue)
val = math.exp(math.log(self.maxValue) - val = math.exp(
absy * span / self.dim.height) math.log(self.maxValue) - absy * span / self.dim.height
)
else: else:
val = self.maxValue - (absy / self.dim.height * self.span) val = self.maxValue - (absy / self.dim.height * self.span)
return [val] return [val]

Wyświetl plik

@ -1,4 +1,3 @@
# NanoVNASaver # NanoVNASaver
# #
# A python program to view and export Touchstone data from a NanoVNA # A python program to view and export Touchstone data from a NanoVNA
@ -27,7 +26,6 @@ logger = logging.getLogger(__name__)
class MagnitudeZSeriesChart(MagnitudeZChart): class MagnitudeZSeriesChart(MagnitudeZChart):
@staticmethod @staticmethod
def magnitude(p: Datapoint) -> float: def magnitude(p: Datapoint) -> float:
return abs(p.seriesImpedance()) return abs(p.seriesImpedance())

Wyświetl plik

@ -1,4 +1,3 @@
# NanoVNASaver # NanoVNASaver
# #
# A python program to view and export Touchstone data from a NanoVNA # A python program to view and export Touchstone data from a NanoVNA
@ -26,7 +25,6 @@ logger = logging.getLogger(__name__)
class MagnitudeZShuntChart(MagnitudeZChart): class MagnitudeZShuntChart(MagnitudeZChart):
@staticmethod @staticmethod
def magnitude(p: Datapoint) -> float: def magnitude(p: Datapoint) -> float:
return abs(p.shuntImpedance()) return abs(p.shuntImpedance())

Wyświetl plik

@ -27,6 +27,7 @@ from NanoVNASaver.RFTools import Datapoint
from NanoVNASaver.SITools import Format, Value from NanoVNASaver.SITools import Format, Value
from NanoVNASaver.Charts.Chart import Chart from NanoVNASaver.Charts.Chart import Chart
from NanoVNASaver.Charts.Frequency import FrequencyChart from NanoVNASaver.Charts.Frequency import FrequencyChart
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -50,19 +51,26 @@ class PermeabilityChart(FrequencyChart):
def drawChart(self, qp: QtGui.QPainter): def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
qp.drawText(self.leftMargin + 5, 15, self.name + qp.drawText(
" (\N{MICRO SIGN}\N{OHM SIGN} / Hz)") self.leftMargin + 5,
15,
self.name + " (\N{MICRO SIGN}\N{OHM SIGN} / Hz)",
)
qp.drawText(10, 15, "R") qp.drawText(10, 15, "R")
qp.drawText(self.leftMargin + self.dim.width + 10, 15, "X") qp.drawText(self.leftMargin + self.dim.width + 10, 15, "X")
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin, qp.drawLine(
self.topMargin - 5, self.leftMargin,
self.leftMargin, self.topMargin - 5,
self.topMargin + self.dim.height + 5) self.leftMargin,
qp.drawLine(self.leftMargin - 5, self.topMargin + self.dim.height + 5,
self.topMargin + self.dim.height, )
self.leftMargin + self.dim.width + 5, qp.drawLine(
self.topMargin + self.dim.height) self.leftMargin - 5,
self.topMargin + self.dim.height,
self.leftMargin + self.dim.width + 5,
self.topMargin + self.dim.height,
)
self.drawTitle(qp) self.drawTitle(qp)
def drawValues(self, qp: QtGui.QPainter): def drawValues(self, qp: QtGui.QPainter):
@ -121,15 +129,16 @@ class PermeabilityChart(FrequencyChart):
for i in range(horizontal_ticks): for i in range(horizontal_ticks):
y = self.topMargin + round(i * self.dim.height / horizontal_ticks) y = self.topMargin + round(i * self.dim.height / horizontal_ticks)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width + 5, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width + 5, y
)
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
val = Value(self.valueAtPosition(y)[0], fmt=fmt) val = Value(self.valueAtPosition(y)[0], fmt=fmt)
qp.drawText(3, y + 4, str(val)) qp.drawText(3, y + 4, str(val))
qp.drawText(3, qp.drawText(
self.dim.height + self.topMargin, 3, self.dim.height + self.topMargin, str(Value(min_val, fmt=fmt))
str(Value(min_val, fmt=fmt))) )
self.drawFrequencyTicks(qp) self.drawFrequencyTicks(qp)
@ -147,8 +156,11 @@ class PermeabilityChart(FrequencyChart):
pen.setColor(c) pen.setColor(c)
qp.setPen(pen) qp.setPen(pen)
qp.drawLine( qp.drawLine(
self.leftMargin + self.dim.width, 9, self.leftMargin + self.dim.width,
self.leftMargin + self.dim.width + 5, 9) 9,
self.leftMargin + self.dim.width + 5,
9,
)
primary_pen.setWidth(self.dim.point) primary_pen.setWidth(self.dim.point)
secondary_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) qp.drawLine(x, y_re, prev_x, prev_y_re)
else: else:
new_x, new_y = self.getPlotable( 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) qp.drawLine(x, y_re, new_x, new_y)
elif self.isPlotable(prev_x, prev_y_re): elif self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, 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) qp.drawLine(x, y_im, prev_x, prev_y_im)
else: else:
new_x, new_y = self.getPlotable( 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) qp.drawLine(x, y_im, new_x, new_y)
elif self.isPlotable(prev_x, prev_y_im): elif self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, 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 = QtGui.QPen(c)
pen.setWidth(2) pen.setWidth(2)
qp.setPen(pen) qp.setPen(pen)
qp.drawLine(self.leftMargin + self.dim.width, 14, qp.drawLine(
self.leftMargin + self.dim.width + 5, 14) self.leftMargin + self.dim.width,
14,
self.leftMargin + self.dim.width + 5,
14,
)
for i, reference in enumerate(self.reference): for i, reference in enumerate(self.reference):
if reference.freq < self.fstart or reference.freq > self.fstop: 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) qp.drawLine(x, y_re, prev_x, prev_y_re)
else: else:
new_x, new_y = self.getPlotable( 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) qp.drawLine(x, y_re, new_x, new_y)
elif self.isPlotable(prev_x, prev_y_re): elif self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, 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) qp.drawLine(x, y_im, prev_x, prev_y_im)
else: else:
new_x, new_y = self.getPlotable( 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) qp.drawLine(x, y_im, new_x, new_y)
elif self.isPlotable(prev_x, prev_y_im): elif self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, 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_re = self.getReYPosition(self.data[m.location])
y_im = self.getImYPosition(self.data[m.location]) y_im = self.getImYPosition(self.data[m.location])
self.drawMarker(x, y_re, qp, m.color, self.drawMarker(x, y_re, qp, m.color, self.markers.index(m) + 1)
self.markers.index(m) + 1) self.drawMarker(x, y_im, 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: def getImYPosition(self, d: Datapoint) -> int:
im = d.impedance().imag im = d.impedance().imag
@ -283,10 +301,12 @@ class PermeabilityChart(FrequencyChart):
else: else:
return -1 return -1
return int( return int(
self.topMargin + (math.log(self.max) - math.log(im)) / self.topMargin
span * self.dim.height) + (math.log(self.max) - math.log(im)) / span * self.dim.height
return int(self.topMargin + (self.max - im) / )
self.span * self.dim.height) return int(
self.topMargin + (self.max - im) / self.span * self.dim.height
)
def getReYPosition(self, d: Datapoint) -> int: def getReYPosition(self, d: Datapoint) -> int:
re = d.impedance().real re = d.impedance().real
@ -298,10 +318,12 @@ class PermeabilityChart(FrequencyChart):
else: else:
return -1 return -1
return int( return int(
self.topMargin + (math.log(self.max) - math.log(re)) / self.topMargin
span * self.dim.height) + (math.log(self.max) - math.log(re)) / span * self.dim.height
)
return int( 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]: def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin absy = y - self.topMargin

Wyświetl plik

@ -50,7 +50,8 @@ class PhaseChart(FrequencyChart):
self.action_unwrap = QtWidgets.QAction("Unwrap") self.action_unwrap = QtWidgets.QAction("Unwrap")
self.action_unwrap.setCheckable(True) self.action_unwrap.setCheckable(True)
self.action_unwrap.triggered.connect( 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) self.y_menu.addAction(self.action_unwrap)
def copy(self): def copy(self):
@ -98,24 +99,32 @@ class PhaseChart(FrequencyChart):
for i in range(tickcount): for i in range(tickcount):
angle = minAngle + span * i / tickcount angle = minAngle + span * i / tickcount
y = self.topMargin + int( 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]: if angle not in [minAngle, maxAngle]:
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
if angle != 0: if angle != 0:
digits = max( digits = max(
0, min(2, math.floor(3 - math.log10(abs(angle))))) 0, min(2, math.floor(3 - math.log10(abs(angle))))
anglestr = str(round(angle)) if digits == 0 else str( )
round(angle, digits)) anglestr = (
str(round(angle))
if digits == 0
else str(round(angle, digits))
)
else: else:
anglestr = "0" anglestr = "0"
qp.drawText(3, y + 3, f"{anglestr}°") qp.drawText(3, y + 3, f"{anglestr}°")
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width, y
qp.drawLine(self.leftMargin - 5, )
self.topMargin, qp.drawLine(
self.leftMargin + self.dim.width, self.leftMargin - 5,
self.topMargin) self.topMargin,
self.leftMargin + self.dim.width,
self.topMargin,
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
qp.drawText(3, self.topMargin + 5, f"{maxAngle}°") qp.drawText(3, self.topMargin + 5, f"{maxAngle}°")
qp.drawText(3, self.dim.height + self.topMargin, f"{minAngle}°") qp.drawText(3, self.dim.height + self.topMargin, f"{minAngle}°")
@ -139,7 +148,8 @@ class PhaseChart(FrequencyChart):
else: else:
angle = math.degrees(d.phase) angle = math.degrees(d.phase)
return self.topMargin + int( 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]: def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin absy = y - self.topMargin

Wyświetl plik

@ -39,16 +39,25 @@ class PolarChart(SquareChart):
qp.setPen(QtGui.QPen(Chart.color.foreground)) 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, height_2)
qp.drawEllipse(QtCore.QPoint(center_x, center_y), qp.drawEllipse(
width_2 // 2, height_2 // 2) QtCore.QPoint(center_x, center_y), width_2 // 2, height_2 // 2
)
qp.drawLine(center_x - width_2, center_y, qp.drawLine(center_x - width_2, center_y, center_x + width_2, center_y)
center_x + width_2, center_y) qp.drawLine(
qp.drawLine(center_x, center_y - height_2, center_x, center_y - height_2, center_x, center_y + height_2
center_x, center_y + height_2) )
qp.drawLine(center_x + width_45, center_y + height_45, qp.drawLine(
center_x - width_45, center_y - height_45) center_x + width_45,
qp.drawLine(center_x + width_45, center_y - height_45, center_y + height_45,
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) self.drawTitle(qp)

Wyświetl plik

@ -57,7 +57,7 @@ class QualityFactorChart(FrequencyChart):
scale = 0 scale = 0
if maxQ > 0: if maxQ > 0:
scale = max(scale, math.floor(math.log10(maxQ))) 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.minQ = self.minDisplayValue
self.maxQ = maxQ self.maxQ = maxQ
@ -69,8 +69,9 @@ class QualityFactorChart(FrequencyChart):
for i in range(tickcount): for i in range(tickcount):
q = self.minQ + i * self.span / tickcount q = self.minQ + i * self.span / tickcount
y = self.topMargin + int((self.maxQ - q) / self.span * y = self.topMargin + int(
self.dim.height) (self.maxQ - q) / self.span * self.dim.height
)
q = round(q) q = round(q)
if q < 10: if q < 10:
q = round(q, 2) q = round(q, 2)
@ -79,12 +80,15 @@ class QualityFactorChart(FrequencyChart):
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
qp.drawText(3, y + 3, str(q)) qp.drawText(3, y + 3, str(q))
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width, y
qp.drawLine(self.leftMargin - 5, )
self.topMargin, qp.drawLine(
self.leftMargin + self.dim.width, self.leftMargin - 5,
self.topMargin) self.topMargin,
self.leftMargin + self.dim.width,
self.topMargin,
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
max_q = round(maxQ) max_q = round(maxQ)
@ -119,8 +123,9 @@ class QualityFactorChart(FrequencyChart):
def getYPosition(self, d: Datapoint) -> int: def getYPosition(self, d: Datapoint) -> int:
Q = d.qFactor() Q = d.qFactor()
return self.topMargin + int((self.maxQ - Q) / self.span * return self.topMargin + int(
self.dim.height) (self.maxQ - Q) / self.span * self.dim.height
)
def valueAtPosition(self, y) -> List[float]: def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin absy = y - self.topMargin

Wyświetl plik

@ -62,11 +62,13 @@ class RealImaginaryChart(FrequencyChart):
self.y_action_automatic.setCheckable(True) self.y_action_automatic.setCheckable(True)
self.y_action_automatic.setChecked(True) self.y_action_automatic.setChecked(True)
self.y_action_automatic.changed.connect( 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 = QtWidgets.QAction("Fixed span")
self.y_action_fixed_span.setCheckable(True) self.y_action_fixed_span.setCheckable(True)
self.y_action_fixed_span.changed.connect( 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 = QtWidgets.QActionGroup(self)
mode_group.addAction(self.y_action_automatic) mode_group.addAction(self.y_action_automatic)
mode_group.addAction(self.y_action_fixed_span) mode_group.addAction(self.y_action_fixed_span)
@ -110,11 +112,14 @@ class RealImaginaryChart(FrequencyChart):
self.drawHorizontalTicks(qp) self.drawHorizontalTicks(qp)
fmt = Format(max_nr_digits=3) fmt = Format(max_nr_digits=3)
qp.drawText(3, self.dim.height + self.topMargin, qp.drawText(
str(Value(min_real, fmt=fmt))) 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, qp.drawText(
str(Value(min_imag, fmt=fmt))) self.leftMargin + self.dim.width + 8,
self.dim.height + self.topMargin,
str(Value(min_imag, fmt=fmt)),
)
self.drawFrequencyTicks(qp) self.drawFrequencyTicks(qp)
@ -131,8 +136,12 @@ class RealImaginaryChart(FrequencyChart):
c.setAlpha(255) c.setAlpha(255)
pen.setColor(c) pen.setColor(c)
qp.setPen(pen) qp.setPen(pen)
qp.drawLine(self.leftMargin + self.dim.width, 9, qp.drawLine(
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) primary_pen.setWidth(self.dim.point)
secondary_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) qp.drawLine(x, y_re, prev_x, prev_y_re)
else: else:
new_x, new_y = self.getPlotable( 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) qp.drawLine(x, y_re, new_x, new_y)
elif self.isPlotable(prev_x, prev_y_re): elif self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, 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) qp.drawLine(x, y_im, prev_x, prev_y_im)
else: else:
new_x, new_y = self.getPlotable( 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) qp.drawLine(x, y_im, new_x, new_y)
elif self.isPlotable(prev_x, prev_y_im): elif self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, 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 = QtGui.QPen(c)
pen.setWidth(2) pen.setWidth(2)
qp.setPen(pen) qp.setPen(pen)
qp.drawLine(self.leftMargin + self.dim.width, 14, qp.drawLine(
self.leftMargin + self.dim.width + 5, 14) self.leftMargin + self.dim.width,
14,
self.leftMargin + self.dim.width + 5,
14,
)
for i, reference in enumerate(self.reference): for i, reference in enumerate(self.reference):
if reference.freq < self.fstart or reference.freq > self.fstop: 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) qp.drawLine(x, y_re, prev_x, prev_y_re)
else: else:
new_x, new_y = self.getPlotable( 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) qp.drawLine(x, y_re, new_x, new_y)
elif self.isPlotable(prev_x, prev_y_re): elif self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, 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) qp.drawLine(x, y_im, prev_x, prev_y_im)
else: else:
new_x, new_y = self.getPlotable( 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) qp.drawLine(x, y_im, new_x, new_y)
elif self.isPlotable(prev_x, prev_y_im): elif self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, 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_re = self.getReYPosition(self.data[m.location])
y_im = self.getImYPosition(self.data[m.location]) y_im = self.getImYPosition(self.data[m.location])
self.drawMarker(x, y_re, qp, m.color, self.drawMarker(x, y_re, qp, m.color, self.markers.index(m) + 1)
self.markers.index(m) + 1) self.drawMarker(x, y_im, 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): def drawHorizontalTicks(self, qp):
# We want one horizontal tick per 50 pixels, at most # We want one horizontal tick per 50 pixels, at most
@ -264,8 +279,9 @@ class RealImaginaryChart(FrequencyChart):
for i in range(horizontal_ticks): for i in range(horizontal_ticks):
y = self.topMargin + i * self.dim.height // horizontal_ticks y = self.topMargin + i * self.dim.height // horizontal_ticks
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width + 5, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width + 5, y
)
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
re = self.max_real - i * self.span_real / horizontal_ticks re = self.max_real - i * self.span_real / horizontal_ticks
im = self.max_imag - i * self.span_imag / horizontal_ticks im = self.max_imag - i * self.span_imag / horizontal_ticks
@ -273,7 +289,8 @@ class RealImaginaryChart(FrequencyChart):
qp.drawText( qp.drawText(
self.leftMargin + self.dim.width + 8, self.leftMargin + self.dim.width + 8,
y + 4, y + 4,
f"{Value(im, fmt=fmt)}") f"{Value(im, fmt=fmt)}",
)
def find_scaling(self): def find_scaling(self):
# Find scaling # Find scaling
@ -350,20 +367,24 @@ class RealImaginaryChart(FrequencyChart):
def getImYPosition(self, d: Datapoint) -> int: def getImYPosition(self, d: Datapoint) -> int:
im = self.value(d).imag im = self.value(d).imag
return int(self.topMargin + (self.max_imag - im) / self.span_imag return int(
* self.dim.height) self.topMargin
+ (self.max_imag - im) / self.span_imag * self.dim.height
)
def getReYPosition(self, d: Datapoint) -> int: def getReYPosition(self, d: Datapoint) -> int:
re = self.value(d).real re = self.value(d).real
return int(self.topMargin + (self.max_real - re) / self.span_real return int(
* self.dim.height if math.isfinite(re) else self.topMargin) 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]: def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin absy = y - self.topMargin
valRe = -1 * ((absy / self.dim.height * valRe = -1 * ((absy / self.dim.height * self.span_real) - self.max_real)
self.span_real) - self.max_real) valIm = -1 * ((absy / self.dim.height * self.span_imag) - self.max_imag)
valIm = -1 * ((absy / self.dim.height *
self.span_imag) - self.max_imag)
return [valRe, valIm] return [valRe, valIm]
def zoomTo(self, x1, y1, x2, y2): def zoomTo(self, x1, y1, x2, y2):
@ -406,9 +427,12 @@ class RealImaginaryChart(FrequencyChart):
def setMinimumRealValue(self): def setMinimumRealValue(self):
min_val, selected = QtWidgets.QInputDialog.getDouble( min_val, selected = QtWidgets.QInputDialog.getDouble(
self, "Minimum real value", self,
"Set minimum real value", value=self.minDisplayReal, "Minimum real value",
decimals=2) "Set minimum real value",
value=self.minDisplayReal,
decimals=2,
)
if not selected: if not selected:
return return
if not (self.fixedValues and min_val >= self.maxDisplayReal): if not (self.fixedValues and min_val >= self.maxDisplayReal):
@ -418,9 +442,12 @@ class RealImaginaryChart(FrequencyChart):
def setMaximumRealValue(self): def setMaximumRealValue(self):
max_val, selected = QtWidgets.QInputDialog.getDouble( max_val, selected = QtWidgets.QInputDialog.getDouble(
self, "Maximum real value", self,
"Set maximum real value", value=self.maxDisplayReal, "Maximum real value",
decimals=2) "Set maximum real value",
value=self.maxDisplayReal,
decimals=2,
)
if not selected: if not selected:
return return
if not (self.fixedValues and max_val <= self.minDisplayReal): if not (self.fixedValues and max_val <= self.minDisplayReal):
@ -430,9 +457,12 @@ class RealImaginaryChart(FrequencyChart):
def setMinimumImagValue(self): def setMinimumImagValue(self):
min_val, selected = QtWidgets.QInputDialog.getDouble( min_val, selected = QtWidgets.QInputDialog.getDouble(
self, "Minimum imaginary value", self,
"Set minimum imaginary value", value=self.minDisplayImag, "Minimum imaginary value",
decimals=2) "Set minimum imaginary value",
value=self.minDisplayImag,
decimals=2,
)
if not selected: if not selected:
return return
if not (self.fixedValues and min_val >= self.maxDisplayImag): if not (self.fixedValues and min_val >= self.maxDisplayImag):
@ -442,9 +472,12 @@ class RealImaginaryChart(FrequencyChart):
def setMaximumImagValue(self): def setMaximumImagValue(self):
max_val, selected = QtWidgets.QInputDialog.getDouble( max_val, selected = QtWidgets.QInputDialog.getDouble(
self, "Maximum imaginary value", self,
"Set maximum imaginary value", value=self.maxDisplayImag, "Maximum imaginary value",
decimals=2) "Set maximum imaginary value",
value=self.maxDisplayImag,
decimals=2,
)
if not selected: if not selected:
return return
if not (self.fixedValues and max_val <= self.minDisplayImag): if not (self.fixedValues and max_val <= self.minDisplayImag):
@ -454,9 +487,10 @@ class RealImaginaryChart(FrequencyChart):
def setFixedValues(self, fixed_values: bool): def setFixedValues(self, fixed_values: bool):
self.fixedValues = fixed_values self.fixedValues = fixed_values
if (fixed_values and if fixed_values and (
(self.minDisplayReal >= self.maxDisplayReal or self.minDisplayReal >= self.maxDisplayReal
self.minDisplayImag > self.maxDisplayImag)): or self.minDisplayImag > self.maxDisplayImag
):
self.fixedValues = False self.fixedValues = False
self.y_action_automatic.setChecked(True) self.y_action_automatic.setChecked(True)
self.y_action_fixed_span.setChecked(False) self.y_action_fixed_span.setChecked(False)
@ -464,17 +498,23 @@ class RealImaginaryChart(FrequencyChart):
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
self.action_set_fixed_start.setText( 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( 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( self.action_set_fixed_minimum_real.setText(
f"Minimum R ({self.minDisplayReal})") f"Minimum R ({self.minDisplayReal})"
)
self.action_set_fixed_maximum_real.setText( self.action_set_fixed_maximum_real.setText(
f"Maximum R ({self.maxDisplayReal})") f"Maximum R ({self.maxDisplayReal})"
)
self.action_set_fixed_minimum_imag.setText( self.action_set_fixed_minimum_imag.setText(
f"Minimum jX ({self.minDisplayImag})") f"Minimum jX ({self.minDisplayImag})"
)
self.action_set_fixed_maximum_imag.setText( self.action_set_fixed_maximum_imag.setText(
f"Maximum jX ({self.maxDisplayImag})") f"Maximum jX ({self.maxDisplayImag})"
)
self.menu.exec_(event.globalPos()) self.menu.exec_(event.globalPos())
def value(self, p: Datapoint) -> complex: def value(self, p: Datapoint) -> complex:

Wyświetl plik

@ -34,30 +34,37 @@ MU = "\N{GREEK SMALL LETTER MU}"
class RealImaginaryMuChart(RealImaginaryChart): class RealImaginaryMuChart(RealImaginaryChart):
def __init__(self, name=""): def __init__(self, name=""):
super().__init__(name) super().__init__(name)
self.y_menu.addSeparator() self.y_menu.addSeparator()
self.action_set_fixed_maximum_real = QtWidgets.QAction( 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.action_set_fixed_maximum_real.triggered.connect(
self.setMaximumRealValue) self.setMaximumRealValue
)
self.action_set_fixed_minimum_real = QtWidgets.QAction( 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.action_set_fixed_minimum_real.triggered.connect(
self.setMinimumRealValue) self.setMinimumRealValue
)
self.action_set_fixed_maximum_imag = QtWidgets.QAction( 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.action_set_fixed_maximum_imag.triggered.connect(
self.setMaximumImagValue) self.setMaximumImagValue
)
self.action_set_fixed_minimum_imag = QtWidgets.QAction( 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.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_maximum_real)
self.y_menu.addAction(self.action_set_fixed_minimum_real) self.y_menu.addAction(self.action_set_fixed_minimum_real)
@ -67,25 +74,21 @@ class RealImaginaryMuChart(RealImaginaryChart):
# Manage core parameters # Manage core parameters
# TODO pick some sane default values? # TODO pick some sane default values?
self.coreLength = 1. self.coreLength = 1.0
self.coreArea = 1. self.coreArea = 1.0
self.coreWindings = 1 self.coreWindings = 1
self.menu.addSeparator() self.menu.addSeparator()
self.action_set_core_length = QtWidgets.QAction( self.action_set_core_length = QtWidgets.QAction("Core effective length")
"Core effective length") self.action_set_core_length.triggered.connect(self.setCoreLength)
self.action_set_core_length.triggered.connect(
self.setCoreLength)
self.action_set_core_area = QtWidgets.QAction( self.action_set_core_area = QtWidgets.QAction("Core area")
"Core area") self.action_set_core_area.triggered.connect(self.setCoreArea)
self.action_set_core_area.triggered.connect(
self.setCoreArea)
self.action_set_core_windings = QtWidgets.QAction( self.action_set_core_windings = QtWidgets.QAction(
"Core number of windings") "Core number of windings"
self.action_set_core_windings.triggered.connect( )
self.setCoreWindings) self.action_set_core_windings.triggered.connect(self.setCoreWindings)
self.menu.addAction(self.action_set_core_length) self.menu.addAction(self.action_set_core_length)
self.menu.addAction(self.action_set_core_area) self.menu.addAction(self.action_set_core_area)
@ -102,41 +105,53 @@ class RealImaginaryMuChart(RealImaginaryChart):
def drawChart(self, qp: QtGui.QPainter): def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
qp.drawText(self.leftMargin + 5, 15, qp.drawText(self.leftMargin + 5, 15, f"{self.name}")
f"{self.name}")
qp.drawText(5, 15, f"{MU}'") qp.drawText(5, 15, f"{MU}'")
qp.drawText(self.leftMargin + self.dim.width + 10, 15, f"{MU}''") qp.drawText(self.leftMargin + self.dim.width + 10, 15, f"{MU}''")
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin, qp.drawLine(
self.topMargin - 5, self.leftMargin,
self.leftMargin, self.topMargin - 5,
self.topMargin + self.dim.height + 5) self.leftMargin,
qp.drawLine(self.leftMargin - 5, self.topMargin + self.dim.height + 5,
self.topMargin + self.dim.height, )
self.leftMargin + self.dim.width + 5, qp.drawLine(
self.topMargin + self.dim.height) self.leftMargin - 5,
self.topMargin + self.dim.height,
self.leftMargin + self.dim.width + 5,
self.topMargin + self.dim.height,
)
self.drawTitle(qp) self.drawTitle(qp)
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
self.action_set_fixed_start.setText( 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( 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( self.action_set_fixed_minimum_real.setText(
f"Minimum {MU}' ({self.minDisplayReal})") f"Minimum {MU}' ({self.minDisplayReal})"
)
self.action_set_fixed_maximum_real.setText( self.action_set_fixed_maximum_real.setText(
f"Maximum {MU}' ({self.maxDisplayReal})") f"Maximum {MU}' ({self.maxDisplayReal})"
)
self.action_set_fixed_minimum_imag.setText( self.action_set_fixed_minimum_imag.setText(
f"Minimum {MU}'' ({self.minDisplayImag})") f"Minimum {MU}'' ({self.minDisplayImag})"
)
self.action_set_fixed_maximum_imag.setText( self.action_set_fixed_maximum_imag.setText(
f"Maximum {MU}'' ({self.maxDisplayImag})") f"Maximum {MU}'' ({self.maxDisplayImag})"
)
self.menu.exec_(event.globalPos()) self.menu.exec_(event.globalPos())
def setCoreLength(self): def setCoreLength(self):
val, selected = QtWidgets.QInputDialog.getDouble( val, selected = QtWidgets.QInputDialog.getDouble(
self, "Core effective length", self,
"Set core effective length in mm", value=self.coreLength, "Core effective length",
decimals=2) "Set core effective length in mm",
value=self.coreLength,
decimals=2,
)
if not selected: if not selected:
return return
if not (self.fixedValues and val >= 0): if not (self.fixedValues and val >= 0):
@ -146,9 +161,12 @@ class RealImaginaryMuChart(RealImaginaryChart):
def setCoreArea(self): def setCoreArea(self):
val, selected = QtWidgets.QInputDialog.getDouble( val, selected = QtWidgets.QInputDialog.getDouble(
self, "Core effective area", self,
"Core effective area",
"Set core cross section area length in mm\N{SUPERSCRIPT TWO}", "Set core cross section area length in mm\N{SUPERSCRIPT TWO}",
value=self.coreArea, decimals=2) value=self.coreArea,
decimals=2,
)
if not selected: if not selected:
return return
if not (self.fixedValues and val >= 0): if not (self.fixedValues and val >= 0):
@ -158,8 +176,11 @@ class RealImaginaryMuChart(RealImaginaryChart):
def setCoreWindings(self): def setCoreWindings(self):
val, selected = QtWidgets.QInputDialog.getInt( val, selected = QtWidgets.QInputDialog.getInt(
self, "Core number of windings", self,
"Set core number of windings", value=self.coreWindings) "Core number of windings",
"Set core number of windings",
value=self.coreWindings,
)
if not selected: if not selected:
return return
if not (self.fixedValues and val >= 0): 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 # Core length and core area are in mm and mm2 respectively
# note: mu_r = mu' - j * mu '' # note: mu_r = mu' - j * mu ''
return np.conj( return np.conj(
inductance * (self.coreLength / 1e3) / inductance
(mu_0 * self.coreWindings**2 * (self.coreArea / 1e6)) * (self.coreLength / 1e3)
/ (mu_0 * self.coreWindings**2 * (self.coreArea / 1e6))
) )

Wyświetl plik

@ -35,24 +35,32 @@ class RealImaginaryZChart(RealImaginaryChart):
self.y_menu.addSeparator() self.y_menu.addSeparator()
self.action_set_fixed_maximum_real = QtWidgets.QAction( 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.action_set_fixed_maximum_real.triggered.connect(
self.setMaximumRealValue) self.setMaximumRealValue
)
self.action_set_fixed_minimum_real = QtWidgets.QAction( 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.action_set_fixed_minimum_real.triggered.connect(
self.setMinimumRealValue) self.setMinimumRealValue
)
self.action_set_fixed_maximum_imag = QtWidgets.QAction( 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.action_set_fixed_maximum_imag.triggered.connect(
self.setMaximumImagValue) self.setMaximumImagValue
)
self.action_set_fixed_minimum_imag = QtWidgets.QAction( 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.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_maximum_real)
self.y_menu.addAction(self.action_set_fixed_minimum_real) self.y_menu.addAction(self.action_set_fixed_minimum_real)
@ -62,34 +70,43 @@ class RealImaginaryZChart(RealImaginaryChart):
def drawChart(self, qp: QtGui.QPainter): def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
qp.drawText(self.leftMargin + 5, 15, qp.drawText(self.leftMargin + 5, 15, f"{self.name} (\N{OHM SIGN})")
f"{self.name} (\N{OHM SIGN})")
qp.drawText(10, 15, "R") qp.drawText(10, 15, "R")
qp.drawText(self.leftMargin + self.dim.width + 10, 15, "X") qp.drawText(self.leftMargin + self.dim.width + 10, 15, "X")
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin, qp.drawLine(
self.topMargin - 5, self.leftMargin,
self.leftMargin, self.topMargin - 5,
self.topMargin + self.dim.height + 5) self.leftMargin,
qp.drawLine(self.leftMargin - 5, self.topMargin + self.dim.height + 5,
self.topMargin + self.dim.height, )
self.leftMargin + self.dim.width + 5, qp.drawLine(
self.topMargin + self.dim.height) self.leftMargin - 5,
self.topMargin + self.dim.height,
self.leftMargin + self.dim.width + 5,
self.topMargin + self.dim.height,
)
self.drawTitle(qp) self.drawTitle(qp)
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
self.action_set_fixed_start.setText( 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( 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( self.action_set_fixed_minimum_real.setText(
f"Minimum R ({self.minDisplayReal})") f"Minimum R ({self.minDisplayReal})"
)
self.action_set_fixed_maximum_real.setText( self.action_set_fixed_maximum_real.setText(
f"Maximum R ({self.maxDisplayReal})") f"Maximum R ({self.maxDisplayReal})"
)
self.action_set_fixed_minimum_imag.setText( self.action_set_fixed_minimum_imag.setText(
f"Minimum jX ({self.minDisplayImag})") f"Minimum jX ({self.minDisplayImag})"
)
self.action_set_fixed_maximum_imag.setText( self.action_set_fixed_maximum_imag.setText(
f"Maximum jX ({self.maxDisplayImag})") f"Maximum jX ({self.maxDisplayImag})"
)
self.menu.exec_(event.globalPos()) self.menu.exec_(event.globalPos())
def value(self, p: Datapoint) -> complex: def value(self, p: Datapoint) -> complex:

Wyświetl plik

@ -25,6 +25,5 @@ logger = logging.getLogger(__name__)
class RealImaginaryZSeriesChart(RealImaginaryZChart): class RealImaginaryZSeriesChart(RealImaginaryZChart):
def impedance(self, p: Datapoint) -> complex: def impedance(self, p: Datapoint) -> complex:
return p.seriesImpedance() return p.seriesImpedance()

Wyświetl plik

@ -25,6 +25,5 @@ logger = logging.getLogger(__name__)
class RealImaginaryZShuntChart(RealImaginaryZChart): class RealImaginaryZShuntChart(RealImaginaryZChart):
def impedance(self, p: Datapoint) -> complex: def impedance(self, p: Datapoint) -> complex:
return p.shuntImpedance() return p.shuntImpedance()

Wyświetl plik

@ -52,14 +52,18 @@ class SParameterChart(FrequencyChart):
qp.drawText(10, 15, "Real") qp.drawText(10, 15, "Real")
qp.drawText(self.leftMargin + self.dim.width - 15, 15, "Imag") qp.drawText(self.leftMargin + self.dim.width - 15, 15, "Imag")
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin, qp.drawLine(
self.topMargin - 5, self.leftMargin,
self.leftMargin, self.topMargin - 5,
self.topMargin + self.dim.height + 5) self.leftMargin,
qp.drawLine(self.leftMargin - 5, self.topMargin + self.dim.height + 5,
self.topMargin + self.dim.height, )
self.leftMargin + self.dim.width, qp.drawLine(
self.topMargin + self.dim.height) self.leftMargin - 5,
self.topMargin + self.dim.height,
self.leftMargin + self.dim.width,
self.topMargin + self.dim.height,
)
def drawValues(self, qp: QtGui.QPainter): def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0: if len(self.data) == 0 and len(self.reference) == 0:
@ -85,44 +89,58 @@ class SParameterChart(FrequencyChart):
val = int(minValue + i * tick_step) val = int(minValue + i * tick_step)
y = self.topMargin + (maxValue - val) // span * self.dim.height y = self.topMargin + (maxValue - val) // span * self.dim.height
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width, y
)
if val > minValue and val != maxValue: if val > minValue and val != maxValue:
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
qp.drawText(3, y + 4, str(round(val, 2))) qp.drawText(3, y + 4, str(round(val, 2)))
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, self.topMargin, qp.drawLine(
self.leftMargin + self.dim.width, self.topMargin) self.leftMargin - 5,
self.topMargin,
self.leftMargin + self.dim.width,
self.topMargin,
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
qp.drawText(3, self.topMargin + 4, f"{maxValue}") qp.drawText(3, self.topMargin + 4, f"{maxValue}")
qp.drawText(3, self.dim.height + self.topMargin, f"{minValue}") qp.drawText(3, self.dim.height + self.topMargin, f"{minValue}")
self.drawFrequencyTicks(qp) self.drawFrequencyTicks(qp)
self.drawData(qp, self.data, Chart.color.sweep, self.getReYPosition) self.drawData(qp, self.data, Chart.color.sweep, self.getReYPosition)
self.drawData(qp, self.reference, Chart.color.reference, self.drawData(
self.getReYPosition) qp, self.reference, Chart.color.reference, self.getReYPosition
self.drawData(qp, self.data, Chart.color.sweep_secondary, )
self.getImYPosition) self.drawData(
self.drawData(qp, self.reference, qp, self.data, Chart.color.sweep_secondary, self.getImYPosition
Chart.color.reference_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.getReYPosition)
self.drawMarkers(qp, y_function=self.getImYPosition) self.drawMarkers(qp, y_function=self.getImYPosition)
def getYPosition(self, d: Datapoint) -> int: def getYPosition(self, d: Datapoint) -> int:
return int( return int(
self.topMargin + (self.maxValue - d.re) / self.span * self.topMargin
self.dim.height) + (self.maxValue - d.re) / self.span * self.dim.height
)
def getReYPosition(self, d: Datapoint) -> int: def getReYPosition(self, d: Datapoint) -> int:
return int( return int(
self.topMargin + (self.maxValue - d.re) / self.span * self.topMargin
self.dim.height) + (self.maxValue - d.re) / self.span * self.dim.height
)
def getImYPosition(self, d: Datapoint) -> int: def getImYPosition(self, d: Datapoint) -> int:
return int( return int(
self.topMargin + (self.maxValue - d.im) / self.span * self.topMargin
self.dim.height) + (self.maxValue - d.im) / self.span * self.dim.height
)
def valueAtPosition(self, y) -> List[float]: def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin absy = y - self.topMargin

Wyświetl plik

@ -35,58 +35,119 @@ class SmithChart(SquareChart):
qp.drawText(3, 15, self.name) qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(Chart.color.foreground)) 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, height_2)
qp.drawLine(center_x - width_2, center_y, qp.drawLine(center_x - width_2, center_y, center_x + width_2, center_y)
center_x + width_2, center_y)
qp.drawEllipse( qp.drawEllipse(
QtCore.QPoint(center_x + int(self.dim.width / 4), center_y), 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( qp.drawEllipse(
QtCore.QPoint(center_x + self.dim.width // 3, center_y), 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( qp.drawEllipse(
QtCore.QPoint(center_x + 3 * self.dim.width // 8, center_y), 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( qp.drawEllipse(
QtCore.QPoint(center_x + 5 * self.dim.width // 12, center_y), 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( qp.drawEllipse(
QtCore.QPoint(center_x + self.dim.width // 6, center_y), 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( qp.drawEllipse(
QtCore.QPoint(center_x + self.dim.width // 12, center_y), 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, qp.drawArc(
self.dim.width // 4, self.dim.width // 4, center_x + 3 * self.dim.width // 8,
90 * 16, 152 * 16) # Im(Z) = -5 center_y,
qp.drawArc(center_x + 3 * self.dim.width // 8, center_y, self.dim.width // 4,
self.dim.width // 4, -self.dim.width // 4, self.dim.width // 4,
-90 * 16, -152 * 16) # Im(Z) = 5 90 * 16,
qp.drawArc(center_x + self.dim.width // 4, center_y, 152 * 16,
width_2, height_2, ) # Im(Z) = -5
90 * 16, 127 * 16) # Im(Z) = -2 qp.drawArc(
qp.drawArc(center_x + self.dim.width // 4, center_y, center_x + 3 * self.dim.width // 8,
width_2, -height_2, center_y,
-90 * 16, -127 * 16) # Im(Z) = 2 self.dim.width // 4,
qp.drawArc(center_x, center_y, -self.dim.width // 4,
self.dim.width, self.dim.height, -90 * 16,
90 * 16, 90 * 16) # Im(Z) = -1 -152 * 16,
qp.drawArc(center_x, center_y, ) # Im(Z) = 5
self.dim.width, - self.dim.height, qp.drawArc(
-90 * 16, -90 * 16) # Im(Z) = 1 center_x + self.dim.width // 4,
qp.drawArc(center_x - width_2, center_y, center_y,
self.dim.width * 2, self.dim.height * 2, width_2,
int(99.5 * 16), int(43.5 * 16)) # Im(Z) = -0.5 height_2,
qp.drawArc(center_x - width_2, center_y, 90 * 16,
self.dim.width * 2, -self.dim.height * 2, 127 * 16,
int(-99.5 * 16), int(-43.5 * 16)) # Im(Z) = 0.5 ) # Im(Z) = -2
qp.drawArc(center_x - self.dim.width * 2, center_y, qp.drawArc(
self.dim.width * 5, self.dim.height * 5, center_x + self.dim.width // 4,
int(93.85 * 16), int(18.85 * 16)) # Im(Z) = -0.2 center_y,
qp.drawArc(center_x - self.dim.width * 2, center_y, width_2,
self.dim.width * 5, -self.dim.height * 5, -height_2,
int(-93.85 * 16), int(-18.85 * 16)) # Im(Z) = 0.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) self.drawTitle(qp)
@ -99,4 +160,6 @@ class SmithChart(SquareChart):
qp.drawEllipse(QtCore.QPoint(center_x, center_y), r, r) qp.drawEllipse(QtCore.QPoint(center_x, center_y), r, r)
qp.drawText( qp.drawText(
QtCore.QRect(center_x - 50, center_y - 4 + r, 100, 20), QtCore.QRect(center_x - 50, center_y - 4 + r, 100, 20),
QtCore.Qt.AlignCenter, f"{swr}") QtCore.Qt.AlignCenter,
f"{swr}",
)

Wyświetl plik

@ -29,11 +29,11 @@ logger = logging.getLogger(__name__)
class SquareChart(Chart): class SquareChart(Chart):
def __init__(self, name=''): def __init__(self, name=""):
super().__init__(name) super().__init__(name)
sizepolicy = QtWidgets.QSizePolicy( sizepolicy = QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.MinimumExpanding
QtWidgets.QSizePolicy.MinimumExpanding) )
self.setSizePolicy(sizepolicy) self.setSizePolicy(sizepolicy)
self.dim.width = 250 self.dim.width = 250
self.dim.height = 250 self.dim.height = 250
@ -53,8 +53,14 @@ class SquareChart(Chart):
def drawChart(self, qp: QtGui.QPainter) -> None: def drawChart(self, qp: QtGui.QPainter) -> None:
raise NotImplementedError() raise NotImplementedError()
def draw_data(self, qp: QtGui.QPainter, color: QtGui.QColor, def draw_data(
data: List[Datapoint], fstart: int = 0, fstop: int = 0): self,
qp: QtGui.QPainter,
color: QtGui.QColor,
data: List[Datapoint],
fstart: int = 0,
fstop: int = 0,
):
if not data: if not data:
return return
fstop = fstop or data[-1].freq fstop = fstop or data[-1].freq
@ -65,8 +71,7 @@ class SquareChart(Chart):
qp.setPen(pen) qp.setPen(pen)
prev_x = self.getXPosition(data[0]) prev_x = self.getXPosition(data[0])
prev_y = int(self.height() / 2 + data[0].im * -1 * prev_y = int(self.height() / 2 + data[0].im * -1 * self.dim.height / 2)
self.dim.height / 2)
for i, d in enumerate(data): for i, d in enumerate(data):
x = self.getXPosition(d) x = self.getXPosition(d)
y = int(self.height() / 2 + d.im * -1 * self.dim.height / 2) 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 fstart = self.data[0].freq if self.data else 0
fstop = self.data[-1].freq if self.data else 0 fstop = self.data[-1].freq if self.data else 0
self.draw_data(qp, Chart.color.reference, self.draw_data(qp, Chart.color.reference, self.reference, fstart, fstop)
self.reference, fstart, fstop)
for m in self.markers: for m in self.markers:
if m.location != -1 and m.location < len(self.data): if m.location != -1 and m.location < len(self.data):
x = self.getXPosition(self.data[m.location]) x = self.getXPosition(self.data[m.location])
y = int(self.height() // 2 - y = int(
self.data[m.location].im * self.dim.height // 2) self.height() // 2
- self.data[m.location].im * self.dim.height // 2
)
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 resizeEvent(self, a0: QtGui.QResizeEvent) -> None: def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
@ -114,11 +120,13 @@ class SquareChart(Chart):
y = a0.y() y = a0.y()
absx = x - (self.width() - self.dim.width) / 2 absx = x - (self.width() - self.dim.width) / 2
absy = y - (self.height() - self.dim.height) / 2 absy = y - (self.height() - self.dim.height) / 2
if (absx < 0 or if (
absx > self.dim.width or absx < 0
absy < 0 or or absx > self.dim.width
absy > self.dim.height or or absy < 0
(not self.data and not self.reference)): or absy > self.dim.height
or (not self.data and not self.reference)
):
a0.ignore() a0.ignore()
return return
a0.accept() a0.accept()
@ -133,8 +141,9 @@ class SquareChart(Chart):
positions = [ positions = [
math.sqrt( math.sqrt(
(x - (width_2 + d.re * dim_x_2))**2 + (x - (width_2 + d.re * dim_x_2)) ** 2
(y - (height_2 - d.im * dim_y_2))**2) + (y - (height_2 - d.im * dim_y_2)) ** 2
)
for d in target for d in target
] ]

Wyświetl plik

@ -49,7 +49,9 @@ class TDRChart(Chart):
self.setSizePolicy( self.setSizePolicy(
QtWidgets.QSizePolicy( QtWidgets.QSizePolicy(
QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding)) QtWidgets.QSizePolicy.MinimumExpanding,
)
)
pal = QtGui.QPalette() pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, Chart.color.background) pal.setColor(QtGui.QPalette.Background, Chart.color.background)
self.setPalette(pal) self.setPalette(pal)
@ -68,11 +70,13 @@ class TDRChart(Chart):
self.action_automatic.setCheckable(True) self.action_automatic.setCheckable(True)
self.action_automatic.setChecked(True) self.action_automatic.setChecked(True)
self.action_automatic.changed.connect( 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 = QtWidgets.QAction("Fixed span")
self.action_fixed_span.setCheckable(True) self.action_fixed_span.setCheckable(True)
self.action_fixed_span.changed.connect( 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_automatic)
self.mode_group.addAction(self.action_fixed_span) self.mode_group.addAction(self.action_fixed_span)
self.x_menu.addAction(self.action_automatic) self.x_menu.addAction(self.action_automatic)
@ -80,11 +84,13 @@ class TDRChart(Chart):
self.x_menu.addSeparator() self.x_menu.addSeparator()
self.action_set_fixed_start = QtWidgets.QAction( 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_start.triggered.connect(self.setMinimumLength)
self.action_set_fixed_stop = QtWidgets.QAction( 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.action_set_fixed_stop.triggered.connect(self.setMaximumLength)
self.x_menu.addAction(self.action_set_fixed_start) 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.setCheckable(True)
self.y_action_automatic.setChecked(True) self.y_action_automatic.setChecked(True)
self.y_action_automatic.changed.connect( 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 = QtWidgets.QAction("Fixed")
self.y_action_fixed.setCheckable(True) self.y_action_fixed.setCheckable(True)
self.y_action_fixed.changed.connect( 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_automatic)
self.y_mode_group.addAction(self.y_action_fixed) self.y_mode_group.addAction(self.y_action_fixed)
self.y_menu.addAction(self.y_action_automatic) self.y_menu.addAction(self.y_action_automatic)
@ -108,14 +116,18 @@ class TDRChart(Chart):
self.y_menu.addSeparator() self.y_menu.addSeparator()
self.y_action_set_fixed_maximum = QtWidgets.QAction( 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.y_action_set_fixed_maximum.triggered.connect(
self.setMaximumImpedance) self.setMaximumImpedance
)
self.y_action_set_fixed_minimum = QtWidgets.QAction( 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.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_maximum)
self.y_menu.addAction(self.y_action_set_fixed_minimum) 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.menu.addAction(self.action_save_screenshot)
self.action_popout = QtWidgets.QAction("Popout chart") self.action_popout = QtWidgets.QAction("Popout chart")
self.action_popout.triggered.connect( self.action_popout.triggered.connect(
lambda: self.popoutRequested.emit(self)) lambda: self.popoutRequested.emit(self)
)
self.menu.addAction(self.action_popout) self.menu.addAction(self.action_popout)
self.dim.width = self.width() - self.leftMargin - self.rightMargin self.dim.width = self.width() - self.leftMargin - self.rightMargin
self.dim.height = self.height() - self.bottomMargin - self.topMargin self.dim.height = self.height() - self.bottomMargin - self.topMargin
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
self.action_set_fixed_start.setText( self.action_set_fixed_start.setText(f"Start ({self.minDisplayLength})")
f"Start ({self.minDisplayLength})") self.action_set_fixed_stop.setText(f"Stop ({self.maxDisplayLength})")
self.action_set_fixed_stop.setText(
f"Stop ({self.maxDisplayLength})")
self.y_action_set_fixed_minimum.setText( self.y_action_set_fixed_minimum.setText(
f"Minimum ({self.minImpedance})") f"Minimum ({self.minImpedance})"
)
self.y_action_set_fixed_maximum.setText( self.y_action_set_fixed_maximum.setText(
f"Maximum ({self.maxImpedance})") f"Maximum ({self.maxImpedance})"
)
self.menu.exec_(event.globalPos()) self.menu.exec_(event.globalPos())
def isPlotable(self, x, y): def isPlotable(self, x, y):
return self.leftMargin <= x <= self.width() - self.rightMargin and \ return (
self.topMargin <= y <= self.height() - self.bottomMargin self.leftMargin <= x <= self.width() - self.rightMargin
and self.topMargin <= y <= self.height() - self.bottomMargin
)
def resetDisplayLimits(self): def resetDisplayLimits(self):
self.fixedSpan = False self.fixedSpan = False
@ -162,9 +177,13 @@ class TDRChart(Chart):
def setMinimumLength(self): def setMinimumLength(self):
min_val, selected = QtWidgets.QInputDialog.getDouble( min_val, selected = QtWidgets.QInputDialog.getDouble(
self, "Start length (m)", self,
"Set start length (m)", value=self.minDisplayLength, "Start length (m)",
min=0, decimals=1) "Set start length (m)",
value=self.minDisplayLength,
min=0,
decimals=1,
)
if not selected: if not selected:
return return
if not (self.fixedSpan and min_val >= self.maxDisplayLength): if not (self.fixedSpan and min_val >= self.maxDisplayLength):
@ -174,9 +193,13 @@ class TDRChart(Chart):
def setMaximumLength(self): def setMaximumLength(self):
max_val, selected = QtWidgets.QInputDialog.getDouble( max_val, selected = QtWidgets.QInputDialog.getDouble(
self, "Stop length (m)", self,
"Set stop length (m)", value=self.minDisplayLength, "Stop length (m)",
min=0.1, decimals=1) "Set stop length (m)",
value=self.minDisplayLength,
min=0.1,
decimals=1,
)
if not selected: if not selected:
return return
if not (self.fixedSpan and max_val <= self.minDisplayLength): if not (self.fixedSpan and max_val <= self.minDisplayLength):
@ -190,10 +213,13 @@ class TDRChart(Chart):
def setMinimumImpedance(self): def setMinimumImpedance(self):
min_val, selected = QtWidgets.QInputDialog.getDouble( min_val, selected = QtWidgets.QInputDialog.getDouble(
self, "Minimum impedance (\N{OHM SIGN})", self,
"Minimum impedance (\N{OHM SIGN})",
"Set minimum impedance (\N{OHM SIGN})", "Set minimum impedance (\N{OHM SIGN})",
value=self.minDisplayLength, value=self.minDisplayLength,
min=0, decimals=1) min=0,
decimals=1,
)
if not selected: if not selected:
return return
if not (self.fixedValues and min_val >= self.maxImpedance): if not (self.fixedValues and min_val >= self.maxImpedance):
@ -203,10 +229,13 @@ class TDRChart(Chart):
def setMaximumImpedance(self): def setMaximumImpedance(self):
max_val, selected = QtWidgets.QInputDialog.getDouble( max_val, selected = QtWidgets.QInputDialog.getDouble(
self, "Maximum impedance (\N{OHM SIGN})", self,
"Maximum impedance (\N{OHM SIGN})",
"Set maximum impedance (\N{OHM SIGN})", "Set maximum impedance (\N{OHM SIGN})",
value=self.minDisplayLength, value=self.minDisplayLength,
min=0.1, decimals=1) min=0.1,
decimals=1,
)
if not selected: if not selected:
return return
if not (self.fixedValues and max_val <= self.minImpedance): 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: if self.dragbox.move_x != -1 and self.dragbox.move_y != -1:
dx = self.dragbox.move_x - a0.x() dx = self.dragbox.move_x - a0.x()
dy = self.dragbox.move_y - a0.y() dy = self.dragbox.move_y - a0.y()
self.zoomTo(self.leftMargin + dx, self.topMargin + dy, self.zoomTo(
self.leftMargin + self.dim.width + dx, self.leftMargin + dx,
self.topMargin + self.dim.height + dy) self.topMargin + dy,
self.leftMargin + self.dim.width + dx,
self.topMargin + self.dim.height + dy,
)
self.dragbox.move_x = a0.x() self.dragbox.move_x = a0.x()
self.dragbox.move_y = a0.y() self.dragbox.move_y = a0.y()
return return
@ -261,13 +293,14 @@ class TDRChart(Chart):
if self.tdrWindow.td: if self.tdrWindow.td:
if self.fixedSpan: if self.fixedSpan:
max_index = np.searchsorted( max_index = np.searchsorted(
self.tdrWindow.distance_axis, self.maxDisplayLength * 2) self.tdrWindow.distance_axis, self.maxDisplayLength * 2
)
min_index = np.searchsorted( 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 x_step = (max_index - min_index) / width
else: else:
max_index = math.ceil( max_index = math.ceil(len(self.tdrWindow.distance_axis) / 2)
len(self.tdrWindow.distance_axis) / 2)
x_step = max_index / width x_step = max_index / width
self.markerLocation = int(round(absx * x_step)) self.markerLocation = int(round(absx * x_step))
@ -282,17 +315,21 @@ class TDRChart(Chart):
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(x, self.topMargin, x, self.topMargin + height) qp.drawLine(x, self.topMargin, x, self.topMargin + height)
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
distance = self.tdrWindow.distance_axis[ distance = (
min_index + self.tdrWindow.distance_axis[
int((x - self.leftMargin) * x_step) - 1] / 2 min_index + int((x - self.leftMargin) * x_step) - 1
qp.drawText(x - 15, self.topMargin + height + 15, ]
f"{round(distance, 1)}m") / 2
)
qp.drawText(
x - 15, self.topMargin + height + 15, f"{round(distance, 1)}m"
)
qp.setPen(QtGui.QPen(Chart.color.text)) qp.setPen(QtGui.QPen(Chart.color.text))
qp.drawText( qp.drawText(
self.leftMargin - 10, self.leftMargin - 10,
self.topMargin + height + 15, self.topMargin + height + 15,
str(round(self.tdrWindow.distance_axis[min_index] / 2, str(round(self.tdrWindow.distance_axis[min_index] / 2, 1)) + "m",
1)) + "m") )
def _draw_y_ticks(self, height, width, min_impedance, max_impedance): def _draw_y_ticks(self, height, width, min_impedance, max_impedance):
qp = QtGui.QPainter(self) qp = QtGui.QPainter(self)
@ -308,7 +345,8 @@ class TDRChart(Chart):
qp.drawText(3, y + 3, str(round(y_val, 1))) qp.drawText(3, y + 3, str(round(y_val, 1)))
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
qp.drawText( 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): def _draw_max_point(self, height, x_step, y_step, min_index):
qp = QtGui.QPainter(self) qp = QtGui.QPainter(self)
@ -316,22 +354,25 @@ class TDRChart(Chart):
max_point = QtCore.QPoint( max_point = QtCore.QPoint(
self.leftMargin + int((id_max - min_index) / x_step), self.leftMargin + int((id_max - min_index) / x_step),
(self.topMargin + height) - int( (self.topMargin + height) - int(self.tdrWindow.td[id_max] / y_step),
self.tdrWindow.td[id_max] / y_step)) )
qp.setPen(self.markers[0].color) qp.setPen(self.markers[0].color)
qp.drawEllipse(max_point, 2, 2) qp.drawEllipse(max_point, 2, 2)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
qp.drawText(max_point.x() - 10, max_point.y() - 5, qp.drawText(
f"{round(self.tdrWindow.distance_axis[id_max] / 2, 2)}m") 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): def _draw_marker(self, height, x_step, y_step, min_index):
qp = QtGui.QPainter(self) qp = QtGui.QPainter(self)
marker_point = QtCore.QPoint( marker_point = QtCore.QPoint(
self.leftMargin + self.leftMargin + int((self.markerLocation - min_index) / x_step),
int((self.markerLocation - min_index) / x_step), (self.topMargin + height)
(self.topMargin + height) - - int(self.tdrWindow.td[self.markerLocation] / y_step),
int(self.tdrWindow.td[self.markerLocation] / y_step)) )
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
qp.drawEllipse(marker_point, 2, 2) qp.drawEllipse(marker_point, 2, 2)
qp.drawText( qp.drawText(
@ -339,19 +380,21 @@ class TDRChart(Chart):
marker_point.y() - 5, marker_point.y() - 5,
f"""{round( f"""{round(
self.tdrWindow.distance_axis[self.markerLocation] / 2, self.tdrWindow.distance_axis[self.markerLocation] / 2,
2)}m""") 2)}m""",
)
def _draw_graph(self, height, width): def _draw_graph(self, height, width):
min_index = 0 min_index = 0
max_index = math.ceil( max_index = math.ceil(len(self.tdrWindow.distance_axis) / 2)
len(self.tdrWindow.distance_axis) / 2)
if self.fixedSpan: if self.fixedSpan:
max_length = max(0.1, self.maxDisplayLength) max_length = max(0.1, self.maxDisplayLength)
max_index = np.searchsorted( max_index = np.searchsorted(
self.tdrWindow.distance_axis, max_length * 2) self.tdrWindow.distance_axis, max_length * 2
)
min_index = np.searchsorted( 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 == min_index:
if max_index < len(self.tdrWindow.distance_axis) - 1: if max_index < len(self.tdrWindow.distance_axis) - 1:
max_index += 1 max_index += 1
@ -361,8 +404,7 @@ class TDRChart(Chart):
# TODO: Limit the search to the selected span? # TODO: Limit the search to the selected span?
min_impedance = max(0, np.min(self.tdrWindow.step_response_Z) / 1.05) min_impedance = max(0, np.min(self.tdrWindow.step_response_Z) / 1.05)
max_impedance = min(1000, np.max( max_impedance = min(1000, np.max(self.tdrWindow.step_response_Z) * 1.05)
self.tdrWindow.step_response_Z) * 1.05)
if self.fixedValues: if self.fixedValues:
min_impedance = max(0, self.minImpedance) min_impedance = max(0, self.minImpedance)
max_impedance = max(0.1, self.maxImpedance) 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 y_step = max(self.tdrWindow.td) * 1.1 / height or 1.0e-30
self._draw_ticks(height, width, x_step, min_index) 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) qp = QtGui.QPainter(self)
pen = QtGui.QPen(Chart.color.sweep) pen = QtGui.QPen(Chart.color.sweep)
@ -388,7 +430,8 @@ class TDRChart(Chart):
x = self.leftMargin + int((i - min_index) / x_step) x = self.leftMargin + int((i - min_index) / x_step)
y = (self.topMargin + height) - int( 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): if self.isPlotable(x, y):
pen.setColor(Chart.color.sweep_secondary) pen.setColor(Chart.color.sweep_secondary)
qp.setPen(pen) qp.setPen(pen)
@ -408,14 +451,18 @@ class TDRChart(Chart):
height = self.height() - self.bottomMargin - self.topMargin height = self.height() - self.bottomMargin - self.topMargin
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, qp.drawLine(
self.height() - self.bottomMargin, self.leftMargin - 5,
self.width() - self.rightMargin, self.height() - self.bottomMargin,
self.height() - self.bottomMargin) self.width() - self.rightMargin,
qp.drawLine(self.leftMargin, self.height() - self.bottomMargin,
self.topMargin - 5, )
self.leftMargin, qp.drawLine(
self.height() - self.bottomMargin + 5) self.leftMargin,
self.topMargin - 5,
self.leftMargin,
self.height() - self.bottomMargin + 5,
)
# Number of ticks does not include the origin # Number of ticks does not include the origin
self.drawTitle(qp) self.drawTitle(qp)
@ -424,12 +471,13 @@ class TDRChart(Chart):
if self.dragbox.state and self.dragbox.pos[0] != -1: if self.dragbox.state and self.dragbox.pos[0] != -1:
dashed_pen = QtGui.QPen( dashed_pen = QtGui.QPen(
Chart.color.foreground, 1, QtCore.Qt.DashLine) Chart.color.foreground, 1, QtCore.Qt.DashLine
)
qp.setPen(dashed_pen) qp.setPen(dashed_pen)
qp.drawRect( qp.drawRect(
QtCore.QRect( QtCore.QRect(
QtCore.QPoint(*self.dragbox.pos_start), 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 max_impedance = self.maxImpedance
else: else:
min_impedance = max( min_impedance = max(
0, 0, np.min(self.tdrWindow.step_response_Z) / 1.05
np.min(self.tdrWindow.step_response_Z) / 1.05) )
max_impedance = min( max_impedance = min(
1000, 1000, np.max(self.tdrWindow.step_response_Z) * 1.05
np.max(self.tdrWindow.step_response_Z) * 1.05) )
y_step = (max_impedance - min_impedance) / height y_step = (max_impedance - min_impedance) / height
return y_step * absy + min_impedance return y_step * absy + min_impedance
return 0 return 0
@ -459,20 +507,28 @@ class TDRChart(Chart):
width = self.width() - self.leftMargin - self.rightMargin width = self.width() - self.leftMargin - self.rightMargin
absx = x - self.leftMargin absx = x - self.leftMargin
min_length = self.minDisplayLength if self.fixedSpan else 0 min_length = self.minDisplayLength if self.fixedSpan else 0
max_length = self.maxDisplayLength if self.fixedSpan else ( max_length = (
self.tdrWindow.distance_axis[ self.maxDisplayLength
math.ceil(len(self.tdrWindow.distance_axis) / 2) if self.fixedSpan
] / 2) else (
self.tdrWindow.distance_axis[
math.ceil(len(self.tdrWindow.distance_axis) / 2)
]
/ 2
)
)
x_step = (max_length - min_length) / width x_step = (max_length - min_length) / width
if limit and absx < 0: if limit and absx < 0:
return min_length return min_length
return (max_length if limit and absx > width else return (
absx * x_step + min_length) max_length if limit and absx > width else absx * x_step + min_length
)
def zoomTo(self, x1, y1, x2, y2): def zoomTo(self, x1, y1, x2, y2):
logger.debug( 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) val1 = self.valueAtPosition(y1)
val2 = self.valueAtPosition(y2) val2 = self.valueAtPosition(y2)

Wyświetl plik

@ -30,7 +30,6 @@ logger = logging.getLogger(__name__)
class VSWRChart(FrequencyChart): class VSWRChart(FrequencyChart):
def __init__(self, name=""): def __init__(self, name=""):
super().__init__(name) super().__init__(name)
@ -90,19 +89,22 @@ class VSWRChart(FrequencyChart):
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
if vswr != 0: if vswr != 0:
digits = max( 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" v_text = f"{round(vswr, digits)}" if digits else "0"
qp.drawText(3, y + 3, v_text) qp.drawText(3, y + 3, v_text)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width, y
qp.drawLine(self.leftMargin - 5, )
self.topMargin + self.dim.height, qp.drawLine(
self.leftMargin + self.dim.width, self.leftMargin - 5,
self.topMargin + self.dim.height) self.topMargin + self.dim.height,
self.leftMargin + self.dim.width,
self.topMargin + self.dim.height,
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
digits = max( digits = max(0, min(2, math.floor(3 - math.log10(abs(minVSWR)))))
0, min(2, math.floor(3 - math.log10(abs(minVSWR)))))
v_text = f"{round(minVSWR, digits)}" if digits else "0" v_text = f"{round(minVSWR, digits)}" if digits else "0"
qp.drawText(3, self.topMargin + self.dim.height, v_text) qp.drawText(3, self.topMargin + self.dim.height, v_text)
else: else:
@ -112,16 +114,20 @@ class VSWRChart(FrequencyChart):
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
if vswr != 0: if vswr != 0:
digits = max( 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" vswrstr = f"{round(vswr, digits)}" if digits else "0"
qp.drawText(3, y + 3, vswrstr) qp.drawText(3, y + 3, vswrstr)
qp.setPen(QtGui.QPen(Chart.color.foreground)) qp.setPen(QtGui.QPen(Chart.color.foreground))
qp.drawLine(self.leftMargin - 5, y, qp.drawLine(
self.leftMargin + self.dim.width, y) self.leftMargin - 5, y, self.leftMargin + self.dim.width, y
qp.drawLine(self.leftMargin - 5, )
self.topMargin, qp.drawLine(
self.leftMargin + self.dim.width, self.leftMargin - 5,
self.topMargin) self.topMargin,
self.leftMargin + self.dim.width,
self.topMargin,
)
qp.setPen(Chart.color.text) qp.setPen(Chart.color.text)
digits = max(0, min(2, math.floor(3 - math.log10(abs(maxVSWR))))) digits = max(0, min(2, math.floor(3 - math.log10(abs(maxVSWR)))))
v_text = f"{round(maxVSWR, digits)}" if digits else "0" v_text = f"{round(maxVSWR, digits)}" if digits else "0"
@ -130,8 +136,7 @@ class VSWRChart(FrequencyChart):
qp.setPen(Chart.color.swr) qp.setPen(Chart.color.swr)
for vswr in self.swrMarkers: for vswr in self.swrMarkers:
y = self.getYPositionFromValue(vswr) y = self.getYPositionFromValue(vswr)
qp.drawLine(self.leftMargin, y, qp.drawLine(self.leftMargin, y, self.leftMargin + self.dim.width, y)
self.leftMargin + self.dim.width, y)
qp.drawText(self.leftMargin + 3, y - 1, str(vswr)) qp.drawText(self.leftMargin + 3, y - 1, str(vswr))
self.drawFrequencyTicks(qp) self.drawFrequencyTicks(qp)
@ -146,13 +151,15 @@ class VSWRChart(FrequencyChart):
span = math.log(self.maxVSWR) - math.log(min_val) span = math.log(self.maxVSWR) - math.log(min_val)
else: else:
return -1 return -1
return ( return self.topMargin + int(
self.topMargin + int( (math.log(self.maxVSWR) - math.log(vswr))
(math.log(self.maxVSWR) - math.log(vswr)) / / span
span * self.dim.height)) * self.dim.height
)
try: try:
return self.topMargin + int( return self.topMargin + int(
(self.maxVSWR - vswr) / self.span * self.dim.height) (self.maxVSWR - vswr) / self.span * self.dim.height
)
except OverflowError: except OverflowError:
return self.topMargin return self.topMargin

Wyświetl plik

@ -23,30 +23,31 @@ from .Smith import SmithChart
from .SParam import SParameterChart from .SParam import SParameterChart
from .TDR import TDRChart from .TDR import TDRChart
from .VSWR import VSWRChart from .VSWR import VSWRChart
__all__ = [ __all__ = [
'Chart', "Chart",
'FrequencyChart', "FrequencyChart",
'PolarChart', "PolarChart",
'SquareChart', "SquareChart",
'CapacitanceChart', "CapacitanceChart",
'InductanceChart', "InductanceChart",
'GroupDelayChart', "GroupDelayChart",
'LogMagChart', "LogMagChart",
'CombinedLogMagChart', "CombinedLogMagChart",
'MagnitudeChart', "MagnitudeChart",
'MagnitudeZChart', "MagnitudeZChart",
'MagnitudeZShuntChart', "MagnitudeZShuntChart",
'MagnitudeZSeriesChart', "MagnitudeZSeriesChart",
'PermeabilityChart', "PermeabilityChart",
'PhaseChart', "PhaseChart",
'QualityFactorChart', "QualityFactorChart",
'RealImaginaryChart', "RealImaginaryChart",
'RealImaginaryMuChart', "RealImaginaryMuChart",
'RealImaginaryZChart', "RealImaginaryZChart",
'RealImaginaryZShuntChart', "RealImaginaryZShuntChart",
'RealImaginaryZSeriesChart', "RealImaginaryZSeriesChart",
'SmithChart', "SmithChart",
'SParameterChart', "SParameterChart",
'TDRChart', "TDRChart",
'VSWRChart', "VSWRChart",
] ]

Wyświetl plik

@ -29,16 +29,16 @@ logger = logging.getLogger(__name__)
class ShowButton(QtWidgets.QPushButton): class ShowButton(QtWidgets.QPushButton):
def setText(self, text: str = ''): def setText(self, text: str = ""):
if not text: if not text:
text = ("Show data" text = (
if Defaults.cfg.gui.markers_hidden else "Hide data") "Show data" if Defaults.cfg.gui.markers_hidden else "Hide data"
)
super().setText(text) super().setText(text)
self.setToolTip("Toggle visibility of marker readings area") self.setToolTip("Toggle visibility of marker readings area")
class MarkerControl(Control): class MarkerControl(Control):
def __init__(self, app: QtWidgets.QWidget): def __init__(self, app: QtWidgets.QWidget):
super().__init__(app, "Markers") super().__init__(app, "Markers")
@ -72,7 +72,8 @@ class MarkerControl(Control):
lock_radiobutton = QtWidgets.QRadioButton("Locked") lock_radiobutton = QtWidgets.QRadioButton("Locked")
lock_radiobutton.setLayoutDirection(QtCore.Qt.RightToLeft) lock_radiobutton.setLayoutDirection(QtCore.Qt.RightToLeft)
lock_radiobutton.setSizePolicy( lock_radiobutton.setSizePolicy(
QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred
)
hbox = QtWidgets.QHBoxLayout() hbox = QtWidgets.QHBoxLayout()
hbox.addWidget(self.showMarkerButton) hbox.addWidget(self.showMarkerButton)
@ -82,8 +83,7 @@ class MarkerControl(Control):
def toggle_frame(self): def toggle_frame(self):
def settings(hidden: bool): def settings(hidden: bool):
Defaults.cfg.gui.markers_hidden = not hidden Defaults.cfg.gui.markers_hidden = not hidden
self.app.marker_frame.setHidden( self.app.marker_frame.setHidden(Defaults.cfg.gui.markers_hidden)
Defaults.cfg.gui.markers_hidden)
self.showMarkerButton.setText() self.showMarkerButton.setText()
self.showMarkerButton.repaint() self.showMarkerButton.repaint()

Wyświetl plik

@ -28,7 +28,6 @@ logger = logging.getLogger(__name__)
class SerialControl(Control): class SerialControl(Control):
def __init__(self, app: QtWidgets.QWidget): def __init__(self, app: QtWidgets.QWidget):
super().__init__(app, "Serial port control") super().__init__(app, "Serial port control")
@ -58,7 +57,8 @@ class SerialControl(Control):
self.btn_settings.setMinimumHeight(20) self.btn_settings.setMinimumHeight(20)
self.btn_settings.setFixedWidth(60) self.btn_settings.setFixedWidth(60)
self.btn_settings.clicked.connect( 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) button_layout.addWidget(self.btn_settings, stretch=0)
self.layout.addRow(button_layout) self.layout.addRow(button_layout)
@ -82,8 +82,9 @@ class SerialControl(Control):
try: try:
self.interface.open() self.interface.open()
except (IOError, AttributeError) as exc: except (IOError, AttributeError) as exc:
logger.error("Tried to open %s and failed: %s", logger.error(
self.interface, exc) "Tried to open %s and failed: %s", self.interface, exc
)
return return
if not self.interface.isOpen(): if not self.interface.isOpen():
logger.error("Unable to open port %s", self.interface) 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) logger.error("Unable to connect to VNA: %s", exc)
self.app.vna.validateInput = self.app.settings.value( self.app.vna.validateInput = self.app.settings.value(
"SerialInputValidation", True, bool) "SerialInputValidation", True, bool
)
# connected # connected
self.btn_toggle.setText("Disconnect") self.btn_toggle.setText("Disconnect")
@ -106,16 +108,20 @@ class SerialControl(Control):
if not frequencies: if not frequencies:
logger.warning("No frequencies read") logger.warning("No frequencies read")
return return
logger.info("Read starting frequency %s and end frequency %s", logger.info(
frequencies[0], frequencies[-1]) "Read starting frequency %s and end frequency %s",
frequencies[0],
frequencies[-1],
)
self.app.sweep_control.set_start(frequencies[0]) self.app.sweep_control.set_start(frequencies[0])
if frequencies[0] < frequencies[-1]: if frequencies[0] < frequencies[-1]:
self.app.sweep_control.set_end(frequencies[-1]) self.app.sweep_control.set_end(frequencies[-1])
else: else:
self.app.sweep_control.set_end( self.app.sweep_control.set_end(
frequencies[0] + frequencies[0]
self.app.vna.datapoints * + self.app.vna.datapoints
self.app.sweep_control.get_segments()) * self.app.sweep_control.get_segments()
)
self.app.sweep_control.set_segments(1) # speed up things self.app.sweep_control.set_segments(1) # speed up things
self.app.sweep_control.update_center_span() self.app.sweep_control.update_center_span()

Wyświetl plik

@ -21,8 +21,10 @@ import logging
from PyQt5 import QtWidgets, QtCore from PyQt5 import QtWidgets, QtCore
from NanoVNASaver.Formatting import ( from NanoVNASaver.Formatting import (
format_frequency_sweep, format_frequency_short, format_frequency_sweep,
parse_frequency) format_frequency_short,
parse_frequency,
)
from NanoVNASaver.Inputs import FrequencyInputWidget from NanoVNASaver.Inputs import FrequencyInputWidget
from NanoVNASaver.Controls.Control import Control from NanoVNASaver.Controls.Control import Control
@ -30,7 +32,6 @@ logger = logging.getLogger(__name__)
class SweepControl(Control): class SweepControl(Control):
def __init__(self, app: QtWidgets.QWidget): def __init__(self, app: QtWidgets.QWidget):
super().__init__(app, "Sweep control") super().__init__(app, "Sweep control")
@ -66,8 +67,7 @@ class SweepControl(Control):
self.input_center.setAlignment(QtCore.Qt.AlignRight) self.input_center.setAlignment(QtCore.Qt.AlignRight)
self.input_center.textEdited.connect(self.update_start_end) self.input_center.textEdited.connect(self.update_start_end)
input_right_layout.addRow(QtWidgets.QLabel( input_right_layout.addRow(QtWidgets.QLabel("Center"), self.input_center)
"Center"), self.input_center)
self.input_span = FrequencyInputWidget() self.input_span = FrequencyInputWidget()
self.input_span.setFixedHeight(20) self.input_span.setFixedHeight(20)
@ -77,7 +77,8 @@ class SweepControl(Control):
input_right_layout.addRow(QtWidgets.QLabel("Span"), self.input_span) input_right_layout.addRow(QtWidgets.QLabel("Span"), self.input_span)
self.input_segments = QtWidgets.QLineEdit( 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.setAlignment(QtCore.Qt.AlignRight)
self.input_segments.setFixedHeight(20) self.input_segments.setFixedHeight(20)
self.input_segments.setFixedWidth(60) self.input_segments.setFixedWidth(60)
@ -85,7 +86,8 @@ class SweepControl(Control):
self.label_step = QtWidgets.QLabel("Hz/step") self.label_step = QtWidgets.QLabel("Hz/step")
self.label_step.setAlignment( self.label_step.setAlignment(
QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
)
segment_layout = QtWidgets.QHBoxLayout() segment_layout = QtWidgets.QHBoxLayout()
segment_layout.addWidget(self.input_segments) segment_layout.addWidget(self.input_segments)
@ -95,7 +97,8 @@ class SweepControl(Control):
btn_settings_window = QtWidgets.QPushButton("Sweep settings ...") btn_settings_window = QtWidgets.QPushButton("Sweep settings ...")
btn_settings_window.setFixedHeight(20) btn_settings_window.setFixedHeight(20)
btn_settings_window.clicked.connect( 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) self.layout.addRow(btn_settings_window)
@ -206,8 +209,7 @@ class SweepControl(Control):
segments = self.get_segments() segments = self.get_segments()
if segments > 0: if segments > 0:
fstep = fspan / (segments * self.app.vna.datapoints - 1) fstep = fspan / (segments * self.app.vna.datapoints - 1)
self.label_step.setText( self.label_step.setText(f"{format_frequency_short(fstep)}/step")
f"{format_frequency_short(fstep)}/step")
self.update_sweep() self.update_sweep()
def update_sweep(self): def update_sweep(self):

Wyświetl plik

@ -43,12 +43,12 @@ class GUI:
@DC.dataclass @DC.dataclass
class ChartsSelected: class ChartsSelected:
chart_00: str = 'S11 Smith Chart' chart_00: str = "S11 Smith Chart"
chart_01: str = 'S11 Return Loss' chart_01: str = "S11 Return Loss"
chart_02: str = 'None' chart_02: str = "None"
chart_10: str = 'S21 Polar Plot' chart_10: str = "S21 Polar Plot"
chart_11: str = 'S21 Gain' chart_11: str = "S21 Gain"
chart_12: str = 'None' chart_12: str = "None"
@DC.dataclass @DC.dataclass
@ -69,33 +69,49 @@ class Chart:
@DC.dataclass @DC.dataclass
class ChartColors: # pylint: disable=too-many-instance-attributes class ChartColors: # pylint: disable=too-many-instance-attributes
background: QColor = DC.field( background: QColor = DC.field(
default_factory=lambda: QColor(QtCore.Qt.white)) default_factory=lambda: QColor(QtCore.Qt.white)
)
foreground: QColor = DC.field( 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: QColor = DC.field(default_factory=lambda: QColor(0, 0, 255, 64))
reference_secondary: QColor = DC.field( 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( sweep: QColor = DC.field(
default_factory=lambda: QColor(QtCore.Qt.darkYellow)) default_factory=lambda: QColor(QtCore.Qt.darkYellow)
)
sweep_secondary: QColor = DC.field( sweep_secondary: QColor = DC.field(
default_factory=lambda: QColor(QtCore.Qt.darkMagenta)) default_factory=lambda: QColor(QtCore.Qt.darkMagenta)
swr: QColor = DC.field( )
default_factory=lambda: QColor(255, 0, 0, 128)) swr: QColor = DC.field(default_factory=lambda: QColor(255, 0, 0, 128))
text: QColor = DC.field( text: QColor = DC.field(default_factory=lambda: QColor(QtCore.Qt.black))
default_factory=lambda: QColor(QtCore.Qt.black)) bands: QColor = DC.field(default_factory=lambda: QColor(128, 128, 128, 48))
bands: QColor = DC.field(
default_factory=lambda: QColor(128, 128, 128, 48))
@DC.dataclass @DC.dataclass
class Markers: class Markers:
active_labels: list = DC.field(default_factory=lambda: [ active_labels: list = DC.field(
"actualfreq", "impedance", "serr", "serl", "serc", "parr", "parlc", default_factory=lambda: [
"vswr", "returnloss", "s11q", "s11phase", "s21gain", "s21phase", "actualfreq",
]) "impedance",
"serr",
"serl",
"serc",
"parr",
"parlc",
"vswr",
"returnloss",
"s11q",
"s11phase",
"s21gain",
"s21phase",
]
)
colored_names: bool = True colored_names: bool = True
color_0: QColor = DC.field( 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_1: QColor = DC.field(default_factory=lambda: QColor(255, 0, 0))
color_2: QColor = DC.field(default_factory=lambda: QColor(0, 255, 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)) 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_5: QColor = DC.field(default_factory=lambda: QColor(255, 0, 255))
color_6: QColor = DC.field(default_factory=lambda: QColor(255, 255, 0)) color_6: QColor = DC.field(default_factory=lambda: QColor(255, 255, 0))
color_7: QColor = DC.field( color_7: QColor = DC.field(
default_factory=lambda: QColor(QtCore.Qt.lightGray)) default_factory=lambda: QColor(QtCore.Qt.lightGray)
)
@DC.dataclass @DC.dataclass
class CFG: class CFG:
gui: object = DC.field( gui: object = DC.field(default_factory=lambda: GUI())
default_factory=lambda: GUI()) charts_selected: object = DC.field(default_factory=lambda: ChartsSelected())
charts_selected: object = DC.field( chart: object = DC.field(default_factory=lambda: Chart())
default_factory=lambda: ChartsSelected()) chart_colors: object = DC.field(default_factory=lambda: ChartColors())
chart: object = DC.field( markers: object = DC.field(default_factory=lambda: Markers())
default_factory=lambda: Chart())
chart_colors: object = DC.field(
default_factory=lambda: ChartColors())
markers: object = DC.field(
default_factory=lambda: Markers())
cfg = CFG() cfg = CFG()
def restore(settings: 'AppSettings') -> CFG: def restore(settings: "AppSettings") -> CFG:
result = CFG() result = CFG()
for field in DC.fields(result): for field in DC.fields(result):
value = settings.restore_dataclass(field.name.upper(), value = settings.restore_dataclass(
getattr(result, field.name)) field.name.upper(), getattr(result, field.name)
)
setattr(result, field.name, value) setattr(result, field.name, value)
logger.debug("restored\n(\n%s\n)", result) logger.debug("restored\n(\n%s\n)", result)
return result return result
def store(settings: 'AppSettings', data: CFG = None) -> None: def store(settings: "AppSettings", data: CFG = None) -> None:
data = data or cfg data = data or cfg
logger.debug("storing\n(\n%s\n)", data) logger.debug("storing\n(\n%s\n)", data)
assert isinstance(data, CFG) assert isinstance(data, CFG)
@ -147,25 +160,25 @@ def from_type(data) -> str:
type_map = { type_map = {
bytearray: lambda x: x.hex(), bytearray: lambda x: x.hex(),
QColor: lambda x: x.getRgb(), QColor: lambda x: x.getRgb(),
QByteArray: lambda x: x.toHex() QByteArray: lambda x: x.toHex(),
} }
return (f"{type_map[type(data)](data)}" if return (
type(data) in type_map else f"{type_map[type(data)](data)}" if type(data) in type_map else f"{data}"
f"{data}") )
def to_type(data: object, data_type: type) -> object: def to_type(data: object, data_type: type) -> object:
type_map = { type_map = {
bool: lambda x: x.lower() == 'true', bool: lambda x: x.lower() == "true",
bytearray: bytearray.fromhex, bytearray: bytearray.fromhex,
list: literal_eval, list: literal_eval,
tuple: literal_eval, tuple: literal_eval,
QColor: lambda x: QColor.fromRgb(*literal_eval(x)), 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 return (
data_type in type_map else type_map[data_type](data) if data_type in type_map else data_type(data)
data_type(data)) )
# noinspection PyDataclass # noinspection PyDataclass
@ -178,8 +191,13 @@ class AppSettings(QSettings):
try: try:
assert isinstance(value, field.type) assert isinstance(value, field.type)
except AssertionError as exc: except AssertionError as exc:
logger.error("%s: %s of type %s is not a %s", logger.error(
name, field.name, type(value), field.type) "%s: %s of type %s is not a %s",
name,
field.name,
type(value),
field.type,
)
raise TypeError from exc raise TypeError from exc
self.setValue(field.name, from_type(value)) self.setValue(field.name, from_type(value))
self.endGroup() self.endGroup()

Wyświetl plik

@ -27,22 +27,27 @@ FMT_FREQ_SHORT = SITools.Format(max_nr_digits=4)
FMT_FREQ_SPACE = SITools.Format(space_str=" ") FMT_FREQ_SPACE = SITools.Format(space_str=" ")
FMT_FREQ_SWEEP = SITools.Format(max_nr_digits=9, allow_strip=True) FMT_FREQ_SWEEP = SITools.Format(max_nr_digits=9, allow_strip=True)
FMT_FREQ_INPUTS = SITools.Format( FMT_FREQ_INPUTS = SITools.Format(
max_nr_digits=10, allow_strip=True, max_nr_digits=10, allow_strip=True, printable_min=0, unprintable_under="- "
printable_min=0, unprintable_under="- ") )
FMT_Q_FACTOR = SITools.Format( FMT_Q_FACTOR = SITools.Format(
max_nr_digits=4, assume_infinity=False, max_nr_digits=4,
min_offset=0, max_offset=0, allow_strip=True) assume_infinity=False,
min_offset=0,
max_offset=0,
allow_strip=True,
)
FMT_GROUP_DELAY = SITools.Format(max_nr_digits=5, space_str=" ") 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_REACT = SITools.Format(max_nr_digits=5, space_str=" ", allow_strip=True)
FMT_COMPLEX = SITools.Format(max_nr_digits=3, allow_strip=True, FMT_COMPLEX = SITools.Format(
printable_min=0, unprintable_under="- ") 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_COMPLEX_NEG = SITools.Format(max_nr_digits=3, allow_strip=True)
FMT_SHORT = SITools.Format(max_nr_digits=4) FMT_SHORT = SITools.Format(max_nr_digits=4)
FMT_WAVELENGTH = SITools.Format(max_nr_digits=4, space_str=" ") FMT_WAVELENGTH = SITools.Format(max_nr_digits=4, space_str=" ")
FMT_PARSE = SITools.Format(parse_sloppy_unit=True, parse_sloppy_kilo=True, FMT_PARSE = SITools.Format(
parse_clamp_min=0) 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_VALUE = SITools.Format(parse_sloppy_unit=True, parse_sloppy_kilo=True)
FMT_VSWR = SITools.Format(max_nr_digits=3) 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: 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: 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 fmt_re = FMT_COMPLEX_NEG if allow_negative else FMT_COMPLEX
re = SITools.Value(z.real, fmt=fmt_re) re = SITools.Value(z.real, fmt=fmt_re)
im = SITools.Value(abs(z.imag), fmt=FMT_COMPLEX) 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: def format_wavelength(length: Number) -> str:
@ -153,10 +158,11 @@ def parse_frequency(freq: str) -> int:
return -1 return -1
def parse_value(val: str, unit: str = "", def parse_value(
fmt: SITools.Format = FMT_PARSE_VALUE) -> float: val: str, unit: str = "", fmt: SITools.Format = FMT_PARSE_VALUE
) -> float:
try: try:
val.replace(',', '.') val.replace(",", ".")
return float(SITools.Value(val, unit, fmt)) return float(SITools.Value(val, unit, fmt))
except (ValueError, IndexError): except (ValueError, IndexError):
return 0.0 return 0.0

Wyświetl plik

@ -43,8 +43,8 @@ USBDevice = namedtuple("Device", "vid pid name")
USBDEVICETYPES = ( USBDEVICETYPES = (
USBDevice(0x0483, 0x5740, "NanoVNA"), USBDevice(0x0483, 0x5740, "NanoVNA"),
USBDevice(0x16c0, 0x0483, "AVNA"), USBDevice(0x16C0, 0x0483, "AVNA"),
USBDevice(0x04b4, 0x0008, "S-A-A-2"), USBDevice(0x04B4, 0x0008, "S-A-A-2"),
) )
RETRIES = 3 RETRIES = 3
TIMEOUT = 0.2 TIMEOUT = 0.2
@ -71,15 +71,21 @@ NAME2DEVICE = {
def _fix_v2_hwinfo(dev): def _fix_v2_hwinfo(dev):
# if dev.hwid == r'PORTS\VID_04B4&PID_0008\DEMO': # if dev.hwid == r'PORTS\VID_04B4&PID_0008\DEMO':
if r'PORTS\VID_04B4&PID_0008' in dev.hwid: if r"PORTS\VID_04B4&PID_0008" in dev.hwid:
dev.vid, dev.pid = 0x04b4, 0x0008 dev.vid, dev.pid = 0x04B4, 0x0008
return dev return dev
def usb_typename(device: ListPortInfo) -> str: def usb_typename(device: ListPortInfo) -> str:
return next((t.name for t in USBDEVICETYPES if return next(
device.vid == t.vid and device.pid == t.pid), (
"") t.name
for t in USBDEVICETYPES
if device.vid == t.vid and device.pid == t.pid
),
"",
)
# Get list of interfaces with VNAs connected # Get list of interfaces with VNAs connected
@ -88,13 +94,18 @@ def get_interfaces() -> List[Interface]:
interfaces = [] interfaces = []
# serial like usb interfaces # serial like usb interfaces
for d in list_ports.comports(): 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) d = _fix_v2_hwinfo(d)
if not (typename := usb_typename(d)): if not (typename := usb_typename(d)):
continue continue
logger.debug("Found %s USB:(%04x:%04x) on port %s", logger.debug(
typename, d.vid, d.pid, d.device) "Found %s USB:(%04x:%04x) on port %s",
iface = Interface('serial', typename) typename,
d.vid,
d.pid,
d.device,
)
iface = Interface("serial", typename)
iface.port = d.device iface.port = d.device
iface.open() iface.open()
iface.comment = get_comment(iface) iface.comment = get_comment(iface)
@ -109,9 +120,8 @@ def get_portinfos() -> List[str]:
portinfos = [] portinfos = []
# serial like usb interfaces # serial like usb interfaces
for d in list_ports.comports(): for d in list_ports.comports():
logger.debug("Found USB:(%04x:%04x) on port %s", logger.debug("Found USB:(%04x:%04x) on port %s", d.vid, d.pid, d.device)
d.vid, d.pid, d.device) iface = Interface("serial", "DEBUG")
iface = Interface('serial', "DEBUG")
iface.port = d.device iface.port = d.device
iface.open() iface.open()
version = detect_version(iface) version = detect_version(iface)
@ -130,19 +140,19 @@ def get_comment(iface: Interface) -> str:
with iface.lock: with iface.lock:
vna_version = detect_version(iface) vna_version = detect_version(iface)
if vna_version == 'v2': if vna_version == "v2":
return "S-A-A-2" return "S-A-A-2"
logger.info("Finding firmware variant...") logger.info("Finding firmware variant...")
info = get_info(iface) info = get_info(iface)
for search, name in ( for search, name in (
("AVNA + Teensy", "AVNA"), ("AVNA + Teensy", "AVNA"),
("NanoVNA-H 4", "H4"), ("NanoVNA-H 4", "H4"),
("NanoVNA-H", "H"), ("NanoVNA-H", "H"),
("NanoVNA-F_V2", "F_V2"), ("NanoVNA-F_V2", "F_V2"),
("NanoVNA-F", "F"), ("NanoVNA-F", "F"),
("NanoVNA", "NanoVNA"), ("NanoVNA", "NanoVNA"),
("tinySA", "tinySA"), ("tinySA", "tinySA"),
): ):
if info.find(search) >= 0: if info.find(search) >= 0:
return name return name
@ -171,7 +181,7 @@ def detect_version(serial_port: serial.Serial) -> str:
if data.startswith("2"): if data.startswith("2"):
return "v2" return "v2"
logger.debug("Retry detection: %s", i + 1) 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 "" return ""

Wyświetl plik

@ -46,7 +46,6 @@ class NanoVNA(VNA):
self._sweepdata = [] self._sweepdata = []
def _get_running_frequencies(self): def _get_running_frequencies(self):
logger.debug("Reading values: frequencies") logger.debug("Reading values: frequencies")
try: try:
frequencies = super().readValues("frequencies") frequencies = super().readValues("frequencies")
@ -61,24 +60,27 @@ class NanoVNA(VNA):
timeout = self.serial.timeout timeout = self.serial.timeout
with self.serial.lock: with self.serial.lock:
drain_serial(self.serial) drain_serial(self.serial)
self.serial.write("capture\r".encode('ascii')) self.serial.write("capture\r".encode("ascii"))
self.serial.readline() self.serial.readline()
self.serial.timeout = 4 self.serial.timeout = 4
image_data = self.serial.read( image_data = self.serial.read(
self.screenwidth * self.screenheight * 2) self.screenwidth * self.screenheight * 2
)
self.serial.timeout = timeout self.serial.timeout = timeout
self.serial.timeout = timeout self.serial.timeout = timeout
return image_data return image_data
def _convert_data(self, image_data: bytes) -> bytes: def _convert_data(self, image_data: bytes) -> bytes:
rgb_data = struct.unpack( rgb_data = struct.unpack(
f">{self.screenwidth * self.screenheight}H", f">{self.screenwidth * self.screenheight}H", image_data
image_data) )
rgb_array = np.array(rgb_data, dtype=np.uint32) rgb_array = np.array(rgb_data, dtype=np.uint32)
return (0xFF000000 + return (
((rgb_array & 0xF800) << 8) + 0xFF000000
((rgb_array & 0x07E0) << 5) + + ((rgb_array & 0xF800) << 8)
((rgb_array & 0x001F) << 3)) + ((rgb_array & 0x07E0) << 5)
+ ((rgb_array & 0x001F) << 3)
)
def getScreenshot(self) -> QtGui.QPixmap: def getScreenshot(self) -> QtGui.QPixmap:
logger.debug("Capturing screenshot...") logger.debug("Capturing screenshot...")
@ -90,12 +92,12 @@ class NanoVNA(VNA):
rgba_array, rgba_array,
self.screenwidth, self.screenwidth,
self.screenheight, self.screenheight,
QtGui.QImage.Format_ARGB32) QtGui.QImage.Format_ARGB32,
)
logger.debug("Captured screenshot") logger.debug("Captured screenshot")
return QtGui.QPixmap(image) return QtGui.QPixmap(image)
except serial.SerialException as exc: except serial.SerialException as exc:
logger.exception( logger.exception("Exception while capturing screenshot: %s", exc)
"Exception while capturing screenshot: %s", exc)
return QtGui.QPixmap() return QtGui.QPixmap()
def resetSweep(self, start: int, stop: int): def resetSweep(self, start: int, stop: int):
@ -125,8 +127,12 @@ class NanoVNA(VNA):
logger.debug("readFrequencies: %s", self.sweep_method) logger.debug("readFrequencies: %s", self.sweep_method)
if self.sweep_method != "scan_mask": if self.sweep_method != "scan_mask":
return super().readFrequencies() return super().readFrequencies()
return [int(line) for line in self.exec_command( return [
f"scan {self.start} {self.stop} {self.datapoints} 0b001")] int(line)
for line in self.exec_command(
f"scan {self.start} {self.stop} {self.datapoints} 0b001"
)
]
def readValues(self, value) -> List[str]: def readValues(self, value) -> List[str]:
if self.sweep_method != "scan_mask": if self.sweep_method != "scan_mask":
@ -137,11 +143,12 @@ class NanoVNA(VNA):
if value == "data 0": if value == "data 0":
self._sweepdata = [] self._sweepdata = []
for line in self.exec_command( 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() data = line.split()
self._sweepdata.append(( self._sweepdata.append(
f"{data[0]} {data[1]}", (f"{data[0]} {data[1]}", f"{data[2]} {data[3]}")
f"{data[2]} {data[3]}")) )
if value == "data 0": if value == "data 0":
return [x[0] for x in self._sweepdata] return [x[0] for x in self._sweepdata]
if value == "data 1": if value == "data 1":

Wyświetl plik

@ -46,10 +46,10 @@ class NanoVNA_F_V2(NanoVNA):
rgba_array, rgba_array,
self.screenwidth, self.screenwidth,
self.screenheight, self.screenheight,
QtGui.QImage.Format_RGB16) QtGui.QImage.Format_RGB16,
)
logger.debug("Captured screenshot") logger.debug("Captured screenshot")
return QtGui.QPixmap(image) return QtGui.QPixmap(image)
except serial.SerialException as exc: except serial.SerialException as exc:
logger.exception( logger.exception("Exception while capturing screenshot: %s", exc)
"Exception while capturing screenshot: %s", exc)
return QtGui.QPixmap() return QtGui.QPixmap()

Wyświetl plik

@ -26,13 +26,13 @@ from NanoVNASaver.Hardware.Serial import Interface
from NanoVNASaver.Hardware.VNA import VNA from NanoVNASaver.Hardware.VNA import VNA
from NanoVNASaver.Version import Version from NanoVNASaver.Version import Version
if platform.system() != 'Windows': if platform.system() != "Windows":
import tty import tty
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_CMD_NOP = 0x00 _CMD_NOP = 0x00
_CMD_INDICATE = 0x0d _CMD_INDICATE = 0x0D
_CMD_READ = 0x10 _CMD_READ = 0x10
_CMD_READ2 = 0x11 _CMD_READ2 = 0x11
_CMD_READ4 = 0x12 _CMD_READ4 = 0x12
@ -49,22 +49,23 @@ _ADDR_SWEEP_POINTS = 0x20
_ADDR_SWEEP_VALS_PER_FREQ = 0x22 _ADDR_SWEEP_VALS_PER_FREQ = 0x22
_ADDR_RAW_SAMPLES_MODE = 0x26 _ADDR_RAW_SAMPLES_MODE = 0x26
_ADDR_VALUES_FIFO = 0x30 _ADDR_VALUES_FIFO = 0x30
_ADDR_DEVICE_VARIANT = 0xf0 _ADDR_DEVICE_VARIANT = 0xF0
_ADDR_PROTOCOL_VERSION = 0xf1 _ADDR_PROTOCOL_VERSION = 0xF1
_ADDR_HARDWARE_REVISION = 0xf2 _ADDR_HARDWARE_REVISION = 0xF2
_ADDR_FW_MAJOR = 0xf3 _ADDR_FW_MAJOR = 0xF3
_ADDR_FW_MINOR = 0xf4 _ADDR_FW_MINOR = 0xF4
WRITE_SLEEP = 0.05 WRITE_SLEEP = 0.05
_ADF4350_TXPOWER_DESC_MAP = { _ADF4350_TXPOWER_DESC_MAP = {
0: '9dB attenuation', 0: "9dB attenuation",
1: '6dB attenuation', 1: "6dB attenuation",
2: '3dB attenuation', 2: "3dB attenuation",
3: 'Maximum', 3: "Maximum",
} }
_ADF4350_TXPOWER_DESC_REV_MAP = { _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): class NanoVNA_V2(VNA):
@ -76,7 +77,7 @@ class NanoVNA_V2(VNA):
def __init__(self, iface: Interface): def __init__(self, iface: Interface):
super().__init__(iface) super().__init__(iface)
if platform.system() != 'Windows': if platform.system() != "Windows":
tty.setraw(self.serial.fd) tty.setraw(self.serial.fd)
# reset protocol to known state # reset protocol to known state
@ -85,8 +86,8 @@ class NanoVNA_V2(VNA):
sleep(WRITE_SLEEP) sleep(WRITE_SLEEP)
# firmware major version of 0xff indicates dfu mode # firmware major version of 0xff indicates dfu mode
if self.version.data["major"] == 0xff: if self.version.data["major"] == 0xFF:
raise IOError('Device is in DFU mode') raise IOError("Device is in DFU mode")
if "S21 hack" in self.features: if "S21 hack" in self.features:
self.valid_datapoints = (101, 11, 51, 201, 301, 501, 1021) 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"}) self.features.update({"Set TX power partial", "Set Average"})
# Can only set ADF4350 power, i.e. for >= 140MHz # Can only set ADF4350 power, i.e. for >= 140MHz
self.txPowerRanges = [ 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: def readFirmware(self) -> str:
@ -135,9 +141,15 @@ class NanoVNA_V2(VNA):
freq_index = -1 freq_index = -1
for i in range(pointstoread): for i in range(pointstoread):
(fwd_real, fwd_imag, rev0_real, rev0_imag, rev1_real, (
rev1_imag, freq_index) = unpack_from( fwd_real,
"<iiiiiihxxxxxx", arr, i * 32) fwd_imag,
rev0_real,
rev0_imag,
rev1_real,
rev1_imag,
freq_index,
) = unpack_from("<iiiiiihxxxxxx", arr, i * 32)
fwd = complex(fwd_real, fwd_imag) fwd = complex(fwd_real, fwd_imag)
refl = complex(rev0_real, rev0_imag) refl = complex(rev0_real, rev0_imag)
thru = complex(rev1_real, rev1_imag) thru = complex(rev1_real, rev1_imag)
@ -158,12 +170,14 @@ class NanoVNA_V2(VNA):
self.serial.write(pack("<Q", 0)) self.serial.write(pack("<Q", 0))
sleep(WRITE_SLEEP) sleep(WRITE_SLEEP)
# cmd: write register 0x30 to clear FIFO # cmd: write register 0x30 to clear FIFO
self.serial.write(pack("<BBB", self.serial.write(
_CMD_WRITE, _ADDR_VALUES_FIFO, 0)) pack("<BBB", _CMD_WRITE, _ADDR_VALUES_FIFO, 0)
)
sleep(WRITE_SLEEP) sleep(WRITE_SLEEP)
# clear sweepdata # clear sweepdata
self._sweepdata = [(complex(), complex())] * ( self._sweepdata = [(complex(), complex())] * (
self.datapoints + s21hack) self.datapoints + s21hack
)
pointstodo = self.datapoints + s21hack pointstodo = self.datapoints + s21hack
# we read at most 255 values at a time and the time required # we read at most 255 values at a time and the time required
# empirically is just over 3 seconds for 101 points or # empirically is just over 3 seconds for 101 points or
@ -174,9 +188,13 @@ class NanoVNA_V2(VNA):
pointstoread = min(255, pointstodo) pointstoread = min(255, pointstodo)
# cmd: read FIFO, addr 0x30 # cmd: read FIFO, addr 0x30
self.serial.write( self.serial.write(
pack("<BBB", pack(
_CMD_READFIFO, _ADDR_VALUES_FIFO, "<BBB",
pointstoread)) _CMD_READFIFO,
_ADDR_VALUES_FIFO,
pointstoread,
)
)
sleep(WRITE_SLEEP) sleep(WRITE_SLEEP)
# each value is 32 bytes # each value is 32 bytes
nBytes = pointstoread * 32 nBytes = pointstoread * 32
@ -185,8 +203,9 @@ class NanoVNA_V2(VNA):
# timeout secs # timeout secs
arr = self.serial.read(nBytes) arr = self.serial.read(nBytes)
if nBytes != len(arr): if nBytes != len(arr):
logger.warning("expected %d bytes, got %d", logger.warning(
nBytes, len(arr)) "expected %d bytes, got %d", nBytes, len(arr)
)
# the way to retry on timeout is keep the data # the way to retry on timeout is keep the data
# already read then try to read the rest of # already read then try to read the rest of
# the data into the array # the data into the array
@ -205,8 +224,7 @@ class NanoVNA_V2(VNA):
idx = 1 if value == "data 1" else 0 idx = 1 if value == "data 1" else 0
return [ return [
f'{str(x[idx].real)} {str(x[idx].imag)}' f"{str(x[idx].real)} {str(x[idx].imag)}" for x in self._sweepdata
for x in self._sweepdata
] ]
def resetSweep(self, start: int, stop: int): def resetSweep(self, start: int, stop: int):
@ -225,15 +243,15 @@ class NanoVNA_V2(VNA):
raise IOError("Timeout reading version registers") raise IOError("Timeout reading version registers")
return Version(f"{resp[0]}.0.{resp[1]}") return Version(f"{resp[0]}.0.{resp[1]}")
def readVersion(self) -> 'Version': def readVersion(self) -> "Version":
result = self._read_version(_ADDR_FW_MAJOR, result = self._read_version(_ADDR_FW_MAJOR, _ADDR_FW_MINOR)
_ADDR_FW_MINOR)
logger.debug("readVersion: %s", result) logger.debug("readVersion: %s", result)
return result return result
def read_board_revision(self) -> 'Version': def read_board_revision(self) -> "Version":
result = self._read_version(_ADDR_DEVICE_VARIANT, result = self._read_version(
_ADDR_HARDWARE_REVISION) _ADDR_DEVICE_VARIANT, _ADDR_HARDWARE_REVISION
)
logger.debug("read_board_revision: %s", result) logger.debug("read_board_revision: %s", result)
return result return result
@ -243,34 +261,41 @@ class NanoVNA_V2(VNA):
return return
self.sweepStartHz = start self.sweepStartHz = start
self.sweepStepHz = step self.sweepStepHz = step
logger.info('NanoVNAV2: set sweep start %d step %d', logger.info(
self.sweepStartHz, self.sweepStepHz) "NanoVNAV2: set sweep start %d step %d",
self.sweepStartHz,
self.sweepStepHz,
)
self._updateSweep() self._updateSweep()
return return
def _updateSweep(self): def _updateSweep(self):
s21hack = "S21 hack" in self.features s21hack = "S21 hack" in self.features
cmd = pack("<BBQ", _CMD_WRITE8, _ADDR_SWEEP_START, cmd = pack(
max(50000, "<BBQ",
int(self.sweepStartHz - (self.sweepStepHz * s21hack)))) _CMD_WRITE8,
cmd += pack("<BBQ", _CMD_WRITE8, _ADDR_SWEEP_START,
_ADDR_SWEEP_STEP, int(self.sweepStepHz)) max(50000, int(self.sweepStartHz - (self.sweepStepHz * s21hack))),
cmd += pack("<BBH", _CMD_WRITE2, )
_ADDR_SWEEP_POINTS, self.datapoints + s21hack) cmd += pack(
cmd += pack("<BBH", _CMD_WRITE2, "<BBQ", _CMD_WRITE8, _ADDR_SWEEP_STEP, int(self.sweepStepHz)
_ADDR_SWEEP_VALS_PER_FREQ, 1) )
cmd += pack(
"<BBH", _CMD_WRITE2, _ADDR_SWEEP_POINTS, self.datapoints + s21hack
)
cmd += pack("<BBH", _CMD_WRITE2, _ADDR_SWEEP_VALS_PER_FREQ, 1)
with self.serial.lock: with self.serial.lock:
self.serial.write(cmd) self.serial.write(cmd)
sleep(WRITE_SLEEP) sleep(WRITE_SLEEP)
def setTXPower(self, freq_range, power_desc): def setTXPower(self, freq_range, power_desc):
if freq_range[0] != 140e6: if freq_range[0] != 140e6:
raise ValueError('Invalid TX power frequency range') raise ValueError("Invalid TX power frequency range")
# 140MHz..max => ADF4350 # 140MHz..max => ADF4350
self._set_register(0x42, _ADF4350_TXPOWER_DESC_REV_MAP[power_desc], 1) self._set_register(0x42, _ADF4350_TXPOWER_DESC_REV_MAP[power_desc], 1)
def _set_register(self, addr, value, size): def _set_register(self, addr, value, size):
packet = b'' packet = b""
if size == 1: if size == 1:
packet = pack("<BBB", _CMD_WRITE, addr, value) packet = pack("<BBB", _CMD_WRITE, addr, value)
elif size == 2: elif size == 2:

Wyświetl plik

@ -41,7 +41,7 @@ def drain_serial(serial_port: serial.Serial):
class Interface(serial.Serial): class Interface(serial.Serial):
def __init__(self, interface_type: str, comment, *args, **kwargs): def __init__(self, interface_type: str, comment, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
assert interface_type in {'serial', 'usb', 'bt', 'network'} assert interface_type in {"serial", "usb", "bt", "network"}
self.type = interface_type self.type = interface_type
self.comment = comment self.comment = comment
self.port = None self.port = None

Wyświetl plik

@ -34,18 +34,17 @@ class TinySA(VNA):
name = "tinySA" name = "tinySA"
screenwidth = 320 screenwidth = 320
screenheight = 240 screenheight = 240
valid_datapoints = (290, ) valid_datapoints = (290,)
def __init__(self, iface: Interface): def __init__(self, iface: Interface):
super().__init__(iface) super().__init__(iface)
self.features = {'Screenshots'} self.features = {"Screenshots"}
logger.debug("Setting initial start,stop") logger.debug("Setting initial start,stop")
self.start, self.stop = self._get_running_frequencies() self.start, self.stop = self._get_running_frequencies()
self.sweep_max_freq_Hz = 950e6 self.sweep_max_freq_Hz = 950e6
self._sweepdata = [] self._sweepdata = []
def _get_running_frequencies(self): def _get_running_frequencies(self):
logger.debug("Reading values: frequencies") logger.debug("Reading values: frequencies")
try: try:
frequencies = super().readValues("frequencies") frequencies = super().readValues("frequencies")
@ -60,24 +59,27 @@ class TinySA(VNA):
timeout = self.serial.timeout timeout = self.serial.timeout
with self.serial.lock: with self.serial.lock:
drain_serial(self.serial) drain_serial(self.serial)
self.serial.write("capture\r".encode('ascii')) self.serial.write("capture\r".encode("ascii"))
self.serial.readline() self.serial.readline()
self.serial.timeout = 4 self.serial.timeout = 4
image_data = self.serial.read( image_data = self.serial.read(
self.screenwidth * self.screenheight * 2) self.screenwidth * self.screenheight * 2
)
self.serial.timeout = timeout self.serial.timeout = timeout
self.serial.timeout = timeout self.serial.timeout = timeout
return image_data return image_data
def _convert_data(self, image_data: bytes) -> bytes: def _convert_data(self, image_data: bytes) -> bytes:
rgb_data = struct.unpack( rgb_data = struct.unpack(
f">{self.screenwidth * self.screenheight}H", f">{self.screenwidth * self.screenheight}H", image_data
image_data) )
rgb_array = np.array(rgb_data, dtype=np.uint32) rgb_array = np.array(rgb_data, dtype=np.uint32)
return (0xFF000000 + return (
((rgb_array & 0xF800) << 8) + 0xFF000000
((rgb_array & 0x07E0) << 5) + + ((rgb_array & 0xF800) << 8)
((rgb_array & 0x001F) << 3)) + ((rgb_array & 0x07E0) << 5)
+ ((rgb_array & 0x001F) << 3)
)
def getScreenshot(self) -> QtGui.QPixmap: def getScreenshot(self) -> QtGui.QPixmap:
logger.debug("Capturing screenshot...") logger.debug("Capturing screenshot...")
@ -89,12 +91,12 @@ class TinySA(VNA):
rgba_array, rgba_array,
self.screenwidth, self.screenwidth,
self.screenheight, self.screenheight,
QtGui.QImage.Format_ARGB32) QtGui.QImage.Format_ARGB32,
)
logger.debug("Captured screenshot") logger.debug("Captured screenshot")
return QtGui.QPixmap(image) return QtGui.QPixmap(image)
except serial.SerialException as exc: except serial.SerialException as exc:
logger.exception( logger.exception("Exception while capturing screenshot: %s", exc)
"Exception while capturing screenshot: %s", exc)
return QtGui.QPixmap() return QtGui.QPixmap()
def resetSweep(self, start: int, stop: int): def resetSweep(self, start: int, stop: int):
@ -113,6 +115,7 @@ class TinySA(VNA):
def readValues(self, value) -> List[str]: def readValues(self, value) -> List[str]:
logger.debug("Read: %s", value) logger.debug("Read: %s", value)
if value == "data 0": if value == "data 0":
self._sweepdata = [f"0 {line.strip()}" self._sweepdata = [
for line in self.exec_command("data")] f"0 {line.strip()}" for line in self.exec_command("data")
]
return self._sweepdata return self._sweepdata

Wyświetl plik

@ -44,8 +44,11 @@ WAIT = 0.05
def _max_retries(bandwidth: int, datapoints: int) -> int: def _max_retries(bandwidth: int, datapoints: int) -> int:
return round(20 + 20 * (datapoints / 101) + return round(
(1000 / bandwidth) ** 1.30 * (datapoints / 101)) 20
+ 20 * (datapoints / 101)
+ (1000 / bandwidth) ** 1.30 * (datapoints / 101)
)
class VNA: class VNA:
@ -94,7 +97,7 @@ class VNA:
logger.debug("exec_command(%s)", command) logger.debug("exec_command(%s)", command)
with self.serial.lock: with self.serial.lock:
drain_serial(self.serial) drain_serial(self.serial)
self.serial.write(f"{command}\r".encode('ascii')) self.serial.write(f"{command}\r".encode("ascii"))
sleep(wait) sleep(wait)
retries = 0 retries = 0
max_retries = _max_retries(self.bandwidth, self.datapoints) max_retries = _max_retries(self.bandwidth, self.datapoints)
@ -137,11 +140,14 @@ class VNA:
result = result.split(" {")[1].strip("}") result = result.split(" {")[1].strip("}")
return sorted([int(i) for i in result.split("|")]) return sorted([int(i) for i in result.split("|")])
except IndexError: except IndexError:
return [1000, ] return [
1000,
]
def set_bandwidth(self, bandwidth: int): def set_bandwidth(self, bandwidth: int):
bw_val = DISLORD_BW[bandwidth] \ bw_val = (
if self.bw_method == "dislord" else bandwidth DISLORD_BW[bandwidth] if self.bw_method == "dislord" else bandwidth
)
result = " ".join(self.exec_command(f"bandwidth {bw_val}")) result = " ".join(self.exec_command(f"bandwidth {bw_val}"))
if self.bw_method == "ttrftech" and result: if self.bw_method == "ttrftech" and result:
raise IOError(f"set_bandwith({bandwidth}: {result}") raise IOError(f"set_bandwith({bandwidth}: {result}")
@ -191,11 +197,10 @@ class VNA:
def readValues(self, value) -> List[str]: def readValues(self, value) -> List[str]:
logger.debug("VNA reading %s", value) logger.debug("VNA reading %s", value)
result = list(self.exec_command(value)) result = list(self.exec_command(value))
logger.debug("VNA done reading %s (%d values)", logger.debug("VNA done reading %s (%d values)", value, len(result))
value, len(result))
return result return result
def readVersion(self) -> 'Version': def readVersion(self) -> "Version":
result = list(self.exec_command("version")) result = list(self.exec_command("version"))
logger.debug("result:\n%s", result) logger.debug("result:\n%s", result)
return Version(result[0]) return Version(result[0])

Wyświetl plik

@ -61,71 +61,91 @@ class DeltaMarker(Marker):
imp = imp_b - imp_a imp = imp_b - imp_a
cap_str = format_capacitance( cap_str = format_capacitance(
RFTools.impedance_to_capacitance(imp_b, s11_b.freq) - RFTools.impedance_to_capacitance(imp_b, s11_b.freq)
RFTools.impedance_to_capacitance(imp_a, s11_a.freq)) - RFTools.impedance_to_capacitance(imp_a, s11_a.freq)
)
ind_str = format_inductance( ind_str = format_inductance(
RFTools.impedance_to_inductance(imp_b, s11_b.freq) - RFTools.impedance_to_inductance(imp_b, s11_b.freq)
RFTools.impedance_to_inductance(imp_a, s11_a.freq)) - RFTools.impedance_to_inductance(imp_a, s11_a.freq)
)
imp_p_a = RFTools.serial_to_parallel(imp_a) imp_p_a = RFTools.serial_to_parallel(imp_a)
imp_p_b = RFTools.serial_to_parallel(imp_b) imp_p_b = RFTools.serial_to_parallel(imp_b)
imp_p = imp_p_b - imp_p_a imp_p = imp_p_b - imp_p_a
cap_p_str = format_capacitance( cap_p_str = format_capacitance(
RFTools.impedance_to_capacitance(imp_p_b, s11_b.freq) - 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_a, s11_a.freq)
)
ind_p_str = format_inductance( ind_p_str = format_inductance(
RFTools.impedance_to_inductance(imp_p_b, s11_b.freq) - 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_a, s11_a.freq)
)
x_str = cap_str if imp.imag < 0 else ind_str 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 x_p_str = cap_p_str if imp_p.imag < 0 else ind_p_str
self.label['actualfreq'].setText( self.label["actualfreq"].setText(
format_frequency_space(s11_b.freq - s11_a.freq)) format_frequency_space(s11_b.freq - s11_a.freq)
self.label['lambda'].setText( )
format_wavelength(s11_b.wavelength - s11_a.wavelength)) self.label["lambda"].setText(
self.label['admittance'].setText(format_complex_adm(imp_p, True)) format_wavelength(s11_b.wavelength - s11_a.wavelength)
self.label['impedance'].setText(format_complex_imp(imp, True)) )
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["parc"].setText(cap_p_str)
self.label['parl'].setText(ind_p_str) self.label["parl"].setText(ind_p_str)
self.label['parlc'].setText(x_p_str) self.label["parlc"].setText(x_p_str)
self.label['parr'].setText(format_resistance(imp_p.real, True)) self.label["parr"].setText(format_resistance(imp_p.real, True))
self.label['returnloss'].setText( self.label["returnloss"].setText(
format_gain(s11_b.gain - s11_a.gain, self.returnloss_is_positive)) format_gain(s11_b.gain - s11_a.gain, self.returnloss_is_positive)
self.label['s11groupdelay'].setText(format_group_delay( )
RFTools.groupDelay(b.s11, 1) - self.label["s11groupdelay"].setText(
RFTools.groupDelay(a.s11, 1))) format_group_delay(
RFTools.groupDelay(b.s11, 1) - RFTools.groupDelay(a.s11, 1)
)
)
self.label['s11mag'].setText( self.label["s11mag"].setText(
format_magnitude(abs(s11_b.z) - abs(s11_a.z))) 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["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"{round(abs(s11_b.z) - abs(s11_a.z), 2)}"
f"{format_phase(s11_b.phase - s11_a.phase)}") 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["s11q"].setText(
self.label['s11z'].setText(format_resistance(abs(imp))) format_q_factor(s11_b.qFactor() - s11_a.qFactor(), True)
self.label['serc'].setText(cap_str) )
self.label['serl'].setText(ind_str) self.label["s11z"].setText(format_resistance(abs(imp)))
self.label['serlc'].setText(x_str) self.label["serc"].setText(cap_str)
self.label['serr'].setText(format_resistance(imp.real, True)) self.label["serl"].setText(ind_str)
self.label['vswr'].setText(format_vswr(s11_b.vswr - s11_a.vswr)) 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): if len(a.s21) == len(a.s11):
s21_a = a.s21[1] s21_a = a.s21[1]
s21_b = b.s21[1] s21_b = b.s21[1]
self.label['s21gain'].setText(format_gain( self.label["s21gain"].setText(format_gain(s21_b.gain - s21_a.gain))
s21_b.gain - s21_a.gain)) self.label["s21groupdelay"].setText(
self.label['s21groupdelay'].setText(format_group_delay( format_group_delay(
(RFTools.groupDelay(b.s21, 1) - (
RFTools.groupDelay(a.s21, 1)) / 2)) RFTools.groupDelay(b.s21, 1)
self.label['s21mag'].setText(format_magnitude( - RFTools.groupDelay(a.s21, 1)
abs(s21_b.z) - abs(s21_a.z))) )
self.label['s21phase'].setText(format_phase( / 2
s21_b.phase - s21_a.phase)) )
self.label['s21polar'].setText( )
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"{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)}"
)

Wyświetl plik

@ -56,10 +56,10 @@ TYPES = (
Label("s21groupdelay", "S21 Group Delay", "S21 Group Delay", False), Label("s21groupdelay", "S21 Group Delay", "S21 Group Delay", False),
Label("s21magshunt", "S21 |Z| shunt", "S21 Z Magnitude shunt", False), Label("s21magshunt", "S21 |Z| shunt", "S21 Z Magnitude shunt", False),
Label("s21magseries", "S21 |Z| series", "S21 Z Magnitude series", False), Label("s21magseries", "S21 |Z| series", "S21 Z Magnitude series", False),
Label("s21realimagshunt", "S21 R+jX shunt", Label("s21realimagshunt", "S21 R+jX shunt", "S21 Z Real+Imag shunt", False),
"S21 Z Real+Imag shunt", False), Label(
Label("s21realimagseries", "S21 R+jX series", "s21realimagseries", "S21 R+jX series", "S21 Z Real+Imag series", False
"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] 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""" """Contains the data area to calculate marker values from"""
def __init__(self, freq: int = 0, def __init__(
s11: List[Datapoint] = None, self,
s21: List[Datapoint] = None): freq: int = 0,
s11: List[Datapoint] = None,
s21: List[Datapoint] = None,
):
self.freq = freq self.freq = freq
self.s11 = [] if s11 is None else s11[:] self.s11 = [] if s11 is None else s11[:]
self.s21 = [] if s21 is None else s21[:] self.s21 = [] if s21 is None else s21[:]
def store(self, index: int, def store(self, index: int, s11: List[Datapoint], s21: List[Datapoint]):
s11: List[Datapoint],
s21: List[Datapoint]):
# handle boundaries # handle boundaries
if index == 0: if index == 0:
index = 1 index = 1
s11 = [s11[0], ] + s11 s11 = [
s11[0],
] + s11
if s21: if s21:
s21 = [s21[0], ] + s21 s21 = [
s21[0],
] + s21
if index == len(s11): if index == len(s11):
s11 += [s11[-1], ] s11 += [
s11[-1],
]
if s21: if s21:
s21 += [s21[-1], ] s21 += [
s21[-1],
]
self.freq = s11[1].freq self.freq = s11[1].freq
self.s11 = s11[index - 1:index + 2] self.s11 = s11[index - 1 : index + 2]
if s21: if s21:
self.s21 = s21[index - 1:index + 2] self.s21 = s21[index - 1 : index + 2]

Wyświetl plik

@ -81,7 +81,8 @@ class Marker(QtCore.QObject, Value):
if self.qsettings: if self.qsettings:
Marker._instances += 1 Marker._instances += 1
Marker.active_labels = self.qsettings.value( Marker.active_labels = self.qsettings.value(
"MarkerFields", defaultValue=default_label_ids()) "MarkerFields", defaultValue=default_label_ids()
)
self.index = Marker._instances self.index = Marker._instances
if not self.name: if not self.name:
@ -92,7 +93,9 @@ class Marker(QtCore.QObject, Value):
self.frequencyInput.setAlignment(QtCore.Qt.AlignRight) self.frequencyInput.setAlignment(QtCore.Qt.AlignRight)
self.frequencyInput.editingFinished.connect( self.frequencyInput.editingFinished.connect(
lambda: self.setFrequency( lambda: self.setFrequency(
parse_frequency(self.frequencyInput.text()))) parse_frequency(self.frequencyInput.text())
)
)
############################################################### ###############################################################
# Data display labels # Data display labels
@ -101,8 +104,8 @@ class Marker(QtCore.QObject, Value):
self.label = { self.label = {
label.label_id: MarkerLabel(label.name) for label in TYPES label.label_id: MarkerLabel(label.name) for label in TYPES
} }
self.label['actualfreq'].setMinimumWidth(100) self.label["actualfreq"].setMinimumWidth(100)
self.label['returnloss'].setMinimumWidth(80) self.label["returnloss"].setMinimumWidth(80)
############################################################### ###############################################################
# Marker control layout # Marker control layout
@ -112,8 +115,11 @@ class Marker(QtCore.QObject, Value):
self.btnColorPicker.setMinimumHeight(20) self.btnColorPicker.setMinimumHeight(20)
self.btnColorPicker.setFixedWidth(20) self.btnColorPicker.setFixedWidth(20)
self.btnColorPicker.clicked.connect( self.btnColorPicker.clicked.connect(
lambda: self.setColor(QtWidgets.QColorDialog.getColor( lambda: self.setColor(
self.color, options=QtWidgets.QColorDialog.ShowAlphaChannel)) QtWidgets.QColorDialog.getColor(
self.color, options=QtWidgets.QColorDialog.ShowAlphaChannel
)
)
) )
self.isMouseControlledRadioButton = QtWidgets.QRadioButton() self.isMouseControlledRadioButton = QtWidgets.QRadioButton()
@ -133,7 +139,9 @@ class Marker(QtCore.QObject, Value):
try: try:
self.setColor( self.setColor(
self.qsettings.value( 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 except AttributeError: # happens when qsettings == None
self.setColor(COLORS[1]) self.setColor(COLORS[1])
except IndexError: except IndexError:
@ -159,8 +167,7 @@ class Marker(QtCore.QObject, Value):
def _add_active_labels(self, label_id, form): def _add_active_labels(self, label_id, form):
if label_id in self.label: if label_id in self.label:
form.addRow( form.addRow(f"{self.label[label_id].name}:", self.label[label_id])
f"{self.label[label_id].name}:", self.label[label_id])
self.label[label_id].show() self.label[label_id].show()
def _size_str(self) -> str: def _size_str(self) -> str:
@ -171,9 +178,9 @@ class Marker(QtCore.QObject, Value):
def setScale(self, scale): def setScale(self, scale):
self.group_box.setMaximumWidth(int(340 * scale)) self.group_box.setMaximumWidth(int(340 * scale))
self.label['actualfreq'].setMinimumWidth(int(100 * scale)) self.label["actualfreq"].setMinimumWidth(int(100 * scale))
self.label['actualfreq'].setMinimumWidth(int(100 * scale)) self.label["actualfreq"].setMinimumWidth(int(100 * scale))
self.label['returnloss'].setMinimumWidth(int(80 * scale)) self.label["returnloss"].setMinimumWidth(int(80 * scale))
if self.coloredText: if self.coloredText:
color_string = QtCore.QVariant(self.color) color_string = QtCore.QVariant(self.color)
color_string.convert(QtCore.QVariant.String) color_string.convert(QtCore.QVariant.String)
@ -259,8 +266,10 @@ class Marker(QtCore.QObject, Value):
upper_stepsize = data[-1].freq - data[-2].freq upper_stepsize = data[-1].freq - data[-2].freq
# We are outside the bounds of the data, so we can't put in a marker # 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 if (
self.freq - upper_stepsize / 2 > max_freq): self.freq + lower_stepsize / 2 < min_freq
or self.freq - upper_stepsize / 2 > max_freq
):
return return
min_distance = max_freq min_distance = max_freq
@ -286,15 +295,16 @@ class Marker(QtCore.QObject, Value):
for v in self.label.values(): for v in self.label.values():
v.setText("") v.setText("")
def updateLabels(self, def updateLabels(
s11: List[RFTools.Datapoint], self, s11: List[RFTools.Datapoint], s21: List[RFTools.Datapoint]
s21: List[RFTools.Datapoint]): ):
if not s11: if not s11:
return return
if self.location == -1: # initial position if self.location == -1: # initial position
try: try:
location = (self.index - 1) / ( location = (self.index - 1) / (
(self._instances - 1) * (len(s11) - 1)) (self._instances - 1) * (len(s11) - 1)
)
self.location = int(location) self.location = int(location)
except ZeroDivisionError: except ZeroDivisionError:
self.location = 0 self.location = 0
@ -309,63 +319,72 @@ class Marker(QtCore.QObject, Value):
imp = _s11.impedance() imp = _s11.impedance()
cap_str = format_capacitance( cap_str = format_capacitance(
RFTools.impedance_to_capacitance(imp, _s11.freq)) RFTools.impedance_to_capacitance(imp, _s11.freq)
)
ind_str = format_inductance( 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) imp_p = RFTools.serial_to_parallel(imp)
cap_p_str = format_capacitance( 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( 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_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 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["actualfreq"].setText(format_frequency_space(_s11.freq))
self.label['lambda'].setText(format_wavelength(_s11.wavelength)) self.label["lambda"].setText(format_wavelength(_s11.wavelength))
self.label['admittance'].setText(format_complex_adm(imp)) self.label["admittance"].setText(format_complex_adm(imp))
self.label['impedance'].setText(format_complex_imp(imp)) self.label["impedance"].setText(format_complex_imp(imp))
self.label['parc'].setText(cap_p_str) self.label["parc"].setText(cap_p_str)
self.label['parl'].setText(ind_p_str) self.label["parl"].setText(ind_p_str)
self.label['parlc'].setText(x_p_str) self.label["parlc"].setText(x_p_str)
self.label['parr'].setText(format_resistance(imp_p.real)) self.label["parr"].setText(format_resistance(imp_p.real))
self.label['returnloss'].setText( self.label["returnloss"].setText(
format_gain(_s11.gain, self.returnloss_is_positive)) format_gain(_s11.gain, self.returnloss_is_positive)
self.label['s11groupdelay'].setText( )
format_group_delay(RFTools.groupDelay(s11, self.location))) self.label["s11groupdelay"].setText(
self.label['s11mag'].setText(format_magnitude(abs(_s11.z))) format_group_delay(RFTools.groupDelay(s11, self.location))
self.label['s11phase'].setText(format_phase(_s11.phase)) )
self.label['s11polar'].setText( self.label["s11mag"].setText(format_magnitude(abs(_s11.z)))
f'{str(round(abs(_s11.z), 2))}{format_phase(_s11.phase)}' 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["s11q"].setText(format_q_factor(_s11.qFactor()))
self.label['s11z'].setText(format_resistance(abs(imp))) self.label["s11z"].setText(format_resistance(abs(imp)))
self.label['serc'].setText(cap_str) self.label["serc"].setText(cap_str)
self.label['serl'].setText(ind_str) self.label["serl"].setText(ind_str)
self.label['serlc'].setText(x_str) self.label["serlc"].setText(x_str)
self.label['serr'].setText(format_resistance(imp.real)) self.label["serr"].setText(format_resistance(imp.real))
self.label['vswr'].setText(format_vswr(_s11.vswr)) self.label["vswr"].setText(format_vswr(_s11.vswr))
if len(s21) == len(s11): if len(s21) == len(s11):
_s21 = s21[self.location] _s21 = s21[self.location]
self.label['s21gain'].setText(format_gain(_s21.gain)) self.label["s21gain"].setText(format_gain(_s21.gain))
self.label['s21groupdelay'].setText( self.label["s21groupdelay"].setText(
format_group_delay(RFTools.groupDelay(s21, self.location) / 2)) 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["s21mag"].setText(format_magnitude(abs(_s21.z)))
self.label['s21polar'].setText( self.label["s21phase"].setText(format_phase(_s21.phase))
f'{str(round(abs(_s21.z), 2))}{format_phase(_s21.phase)}' self.label["s21polar"].setText(
f"{str(round(abs(_s21.z), 2))}{format_phase(_s21.phase)}"
) )
self.label['s21magshunt'].setText( self.label["s21magshunt"].setText(
format_magnitude(abs(_s21.shuntImpedance()))) format_magnitude(abs(_s21.shuntImpedance()))
self.label['s21magseries'].setText( )
format_magnitude(abs(_s21.seriesImpedance()))) self.label["s21magseries"].setText(
self.label['s21realimagshunt'].setText( format_magnitude(abs(_s21.seriesImpedance()))
format_complex_imp( )
_s21.shuntImpedance(), allow_negative=True)) self.label["s21realimagshunt"].setText(
self.label['s21realimagseries'].setText( format_complex_imp(_s21.shuntImpedance(), allow_negative=True)
format_complex_imp( )
_s21.seriesImpedance(), allow_negative=True)) self.label["s21realimagseries"].setText(
format_complex_imp(_s21.seriesImpedance(), allow_negative=True)
)

Wyświetl plik

@ -26,9 +26,14 @@ from PyQt5 import QtWidgets, QtCore, QtGui
from NanoVNASaver import Defaults from NanoVNASaver import Defaults
from .Windows import ( from .Windows import (
AboutWindow, AnalysisWindow, CalibrationWindow, AboutWindow,
DeviceSettingsWindow, DisplaySettingsWindow, SweepSettingsWindow, AnalysisWindow,
TDRWindow, FilesWindow CalibrationWindow,
DeviceSettingsWindow,
DisplaySettingsWindow,
SweepSettingsWindow,
TDRWindow,
FilesWindow,
) )
from .Controls.MarkerControl import MarkerControl from .Controls.MarkerControl import MarkerControl
from .Controls.SweepControl import SweepControl from .Controls.SweepControl import SweepControl
@ -40,14 +45,26 @@ from .RFTools import corr_att_data
from .Charts.Chart import Chart from .Charts.Chart import Chart
from .Charts import ( from .Charts import (
CapacitanceChart, CapacitanceChart,
CombinedLogMagChart, GroupDelayChart, InductanceChart, CombinedLogMagChart,
LogMagChart, PhaseChart, GroupDelayChart,
MagnitudeChart, MagnitudeZChart, MagnitudeZShuntChart, InductanceChart,
LogMagChart,
PhaseChart,
MagnitudeChart,
MagnitudeZChart,
MagnitudeZShuntChart,
MagnitudeZSeriesChart, MagnitudeZSeriesChart,
QualityFactorChart, VSWRChart, PermeabilityChart, PolarChart, QualityFactorChart,
VSWRChart,
PermeabilityChart,
PolarChart,
RealImaginaryMuChart, RealImaginaryMuChart,
RealImaginaryZChart, RealImaginaryZShuntChart, RealImaginaryZSeriesChart, RealImaginaryZChart,
SmithChart, SParameterChart, TDRChart, RealImaginaryZShuntChart,
RealImaginaryZSeriesChart,
SmithChart,
SParameterChart,
TDRChart,
) )
from .Calibration import Calibration from .Calibration import Calibration
from .Marker.Widget import Marker from .Marker.Widget import Marker
@ -69,10 +86,11 @@ class NanoVNASaver(QtWidgets.QWidget):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.s21att = 0.0 self.s21att = 0.0
if getattr(sys, 'frozen', False): if getattr(sys, "frozen", False):
logger.debug("Running from pyinstaller bundle") logger.debug("Running from pyinstaller bundle")
self.icon = QtGui.QIcon( 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: else:
self.icon = QtGui.QIcon("icon_48x48.png") self.icon = QtGui.QIcon("icon_48x48.png")
self.setWindowIcon(self.icon) self.setWindowIcon(self.icon)
@ -80,7 +98,8 @@ class NanoVNASaver(QtWidgets.QWidget):
QtCore.QSettings.IniFormat, QtCore.QSettings.IniFormat,
QtCore.QSettings.UserScope, QtCore.QSettings.UserScope,
"NanoVNASaver", "NanoVNASaver",
"NanoVNASaver") "NanoVNASaver",
)
logger.info("Settings from: %s", self.settings.fileName()) logger.info("Settings from: %s", self.settings.fileName())
Defaults.cfg = Defaults.restore(self.settings) Defaults.cfg = Defaults.restore(self.settings)
self.threadpool = QtCore.QThreadPool() self.threadpool = QtCore.QThreadPool()
@ -128,13 +147,17 @@ class NanoVNASaver(QtWidgets.QWidget):
outer.addWidget(scrollarea) outer.addWidget(scrollarea)
self.setLayout(outer) self.setLayout(outer)
scrollarea.setWidgetResizable(True) scrollarea.setWidgetResizable(True)
self.resize(Defaults.cfg.gui.window_width, self.resize(
Defaults.cfg.gui.window_height) Defaults.cfg.gui.window_width, Defaults.cfg.gui.window_height
)
scrollarea.setSizePolicy( scrollarea.setSizePolicy(
QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding) QtWidgets.QSizePolicy.MinimumExpanding,
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, )
QtWidgets.QSizePolicy.MinimumExpanding) self.setSizePolicy(
QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding,
)
widget = QtWidgets.QWidget() widget = QtWidgets.QWidget()
widget.setLayout(layout) widget.setLayout(layout)
scrollarea.setWidget(widget) scrollarea.setWidget(widget)
@ -149,25 +172,30 @@ class NanoVNASaver(QtWidgets.QWidget):
"magnitude_z": MagnitudeZChart("S11 |Z|"), "magnitude_z": MagnitudeZChart("S11 |Z|"),
"permeability": PermeabilityChart( "permeability": PermeabilityChart(
"S11 R/\N{GREEK SMALL LETTER OMEGA} &" "S11 R/\N{GREEK SMALL LETTER OMEGA} &"
" X/\N{GREEK SMALL LETTER OMEGA}"), " X/\N{GREEK SMALL LETTER OMEGA}"
),
"phase": PhaseChart("S11 Phase"), "phase": PhaseChart("S11 Phase"),
"q_factor": QualityFactorChart("S11 Quality Factor"), "q_factor": QualityFactorChart("S11 Quality Factor"),
"real_imag": RealImaginaryZChart("S11 R+jX"), "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"), "smith": SmithChart("S11 Smith Chart"),
"s_parameter": SParameterChart("S11 Real/Imaginary"), "s_parameter": SParameterChart("S11 Real/Imaginary"),
"vswr": VSWRChart("S11 VSWR"), "vswr": VSWRChart("S11 VSWR"),
}, },
"s21": { "s21": {
"group_delay": GroupDelayChart("S21 Group Delay", "group_delay": GroupDelayChart(
reflective=False), "S21 Group Delay", reflective=False
),
"log_mag": LogMagChart("S21 Gain"), "log_mag": LogMagChart("S21 Gain"),
"magnitude": MagnitudeChart("|S21|"), "magnitude": MagnitudeChart("|S21|"),
"magnitude_z_shunt": MagnitudeZShuntChart("S21 |Z| shunt"), "magnitude_z_shunt": MagnitudeZShuntChart("S21 |Z| shunt"),
"magnitude_z_series": MagnitudeZSeriesChart("S21 |Z| series"), "magnitude_z_series": MagnitudeZSeriesChart("S21 |Z| series"),
"real_imag_shunt": RealImaginaryZShuntChart("S21 R+jX shunt"), "real_imag_shunt": RealImaginaryZShuntChart("S21 R+jX shunt"),
"real_imag_series": RealImaginaryZSeriesChart( "real_imag_series": RealImaginaryZSeriesChart(
"S21 R+jX series"), "S21 R+jX series"
),
"phase": PhaseChart("S21 Phase"), "phase": PhaseChart("S21 Phase"),
"polar": PolarChart("S21 Polar Plot"), "polar": PolarChart("S21 Polar Plot"),
"s_parameter": SParameterChart("S21 Real/Imaginary"), "s_parameter": SParameterChart("S21 Real/Imaginary"),
@ -190,8 +218,13 @@ class NanoVNASaver(QtWidgets.QWidget):
# List of all charts that can be selected for display # List of all charts that can be selected for display
self.selectable_charts = ( self.selectable_charts = (
self.s11charts + self.s21charts + self.s11charts
self.combinedCharts + [self.tdr_mainwindow_chart, ]) + self.s21charts
+ self.combinedCharts
+ [
self.tdr_mainwindow_chart,
]
)
# List of all charts that subscribe to updates (including duplicates!) # List of all charts that subscribe to updates (including duplicates!)
self.subscribing_charts = [] self.subscribing_charts = []
@ -314,7 +347,8 @@ class NanoVNASaver(QtWidgets.QWidget):
btn_show_analysis = QtWidgets.QPushButton("Analysis ...") btn_show_analysis = QtWidgets.QPushButton("Analysis ...")
btn_show_analysis.setMinimumHeight(20) btn_show_analysis.setMinimumHeight(20)
btn_show_analysis.clicked.connect( btn_show_analysis.clicked.connect(
lambda: self.display_window("analysis")) lambda: self.display_window("analysis")
)
self.marker_column.addWidget(btn_show_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 = QtWidgets.QLabel()
self.tdr_result_label.setMinimumHeight(20) self.tdr_result_label.setMinimumHeight(20)
tdr_control_layout.addRow( tdr_control_layout.addRow(
"Estimated cable length:", self.tdr_result_label) "Estimated cable length:", self.tdr_result_label
)
self.tdr_button = QtWidgets.QPushButton( self.tdr_button = QtWidgets.QPushButton("Time Domain Reflectometry ...")
"Time Domain Reflectometry ...")
self.tdr_button.setMinimumHeight(20) self.tdr_button.setMinimumHeight(20)
self.tdr_button.clicked.connect(lambda: self.display_window("tdr")) self.tdr_button.clicked.connect(lambda: self.display_window("tdr"))
@ -351,8 +385,13 @@ class NanoVNASaver(QtWidgets.QWidget):
############################################################### ###############################################################
left_column.addSpacerItem( left_column.addSpacerItem(
QtWidgets.QSpacerItem(1, 1, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSpacerItem(
QtWidgets.QSizePolicy.Expanding)) 1,
1,
QtWidgets.QSizePolicy.Fixed,
QtWidgets.QSizePolicy.Expanding,
)
)
############################################################### ###############################################################
# Reference control # Reference control
@ -390,7 +429,8 @@ class NanoVNASaver(QtWidgets.QWidget):
btnOpenCalibrationWindow.setMinimumHeight(20) btnOpenCalibrationWindow.setMinimumHeight(20)
self.calibrationWindow = CalibrationWindow(self) self.calibrationWindow = CalibrationWindow(self)
btnOpenCalibrationWindow.clicked.connect( btnOpenCalibrationWindow.clicked.connect(
lambda: self.display_window("calibration")) lambda: self.display_window("calibration")
)
############################################################### ###############################################################
# Display setup # Display setup
@ -399,22 +439,21 @@ class NanoVNASaver(QtWidgets.QWidget):
btn_display_setup = QtWidgets.QPushButton("Display setup ...") btn_display_setup = QtWidgets.QPushButton("Display setup ...")
btn_display_setup.setMinimumHeight(20) btn_display_setup.setMinimumHeight(20)
btn_display_setup.setMaximumWidth(240) btn_display_setup.setMaximumWidth(240)
btn_display_setup.clicked.connect( btn_display_setup.clicked.connect(lambda: self.display_window("setup"))
lambda: self.display_window("setup"))
btn_about = QtWidgets.QPushButton("About ...") btn_about = QtWidgets.QPushButton("About ...")
btn_about.setMinimumHeight(20) btn_about.setMinimumHeight(20)
btn_about.setMaximumWidth(240) btn_about.setMaximumWidth(240)
btn_about.clicked.connect( btn_about.clicked.connect(lambda: self.display_window("about"))
lambda: self.display_window("about"))
btn_open_file_window = QtWidgets.QPushButton("Files") btn_open_file_window = QtWidgets.QPushButton("Files")
btn_open_file_window.setMinimumHeight(20) btn_open_file_window.setMinimumHeight(20)
btn_open_file_window.setMaximumWidth(240) btn_open_file_window.setMaximumWidth(240)
btn_open_file_window.clicked.connect( btn_open_file_window.clicked.connect(
lambda: self.display_window("file")) lambda: self.display_window("file")
)
button_grid = QtWidgets.QGridLayout() button_grid = QtWidgets.QGridLayout()
button_grid.addWidget(btn_open_file_window, 0, 0) button_grid.addWidget(btn_open_file_window, 0, 0)
@ -484,8 +523,7 @@ class NanoVNASaver(QtWidgets.QWidget):
m2 = Marker("Reference") m2 = Marker("Reference")
m2.location = self.markers[0].location m2.location = self.markers[0].location
m2.resetLabels() m2.resetLabels()
m2.updateLabels(self.ref_data.s11, m2.updateLabels(self.ref_data.s11, self.ref_data.s21)
self.ref_data.s21)
else: else:
logger.warning("No reference data for marker") logger.warning("No reference data for marker")
@ -525,7 +563,8 @@ class NanoVNASaver(QtWidgets.QWidget):
min_vswr = min(s11, key=lambda data: data.vswr) min_vswr = min(s11, key=lambda data: data.vswr)
self.s11_min_swr_label.setText( self.s11_min_swr_label.setText(
f"{format_vswr(min_vswr.vswr)} @" 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)) self.s11_min_rl_label.setText(format_gain(min_vswr.gain))
else: else:
self.s11_min_swr_label.setText("") self.s11_min_swr_label.setText("")
@ -536,10 +575,12 @@ class NanoVNASaver(QtWidgets.QWidget):
max_gain = max(s21, key=lambda data: data.gain) max_gain = max(s21, key=lambda data: data.gain)
self.s21_min_gain_label.setText( self.s21_min_gain_label.setText(
f"{format_gain(min_gain.gain)}" f"{format_gain(min_gain.gain)}"
f" @ {format_frequency(min_gain.freq)}") f" @ {format_frequency(min_gain.freq)}"
)
self.s21_max_gain_label.setText( self.s21_max_gain_label.setText(
f"{format_gain(max_gain.gain)}" f"{format_gain(max_gain.gain)}"
f" @ {format_frequency(max_gain.freq)}") f" @ {format_frequency(max_gain.freq)}"
)
else: else:
self.s21_min_gain_label.setText("") self.s21_min_gain_label.setText("")
self.s21_max_gain_label.setText("") self.s21_max_gain_label.setText("")
@ -551,8 +592,7 @@ class NanoVNASaver(QtWidgets.QWidget):
self._sweep_control(start=False) self._sweep_control(start=False)
for marker in self.markers: for marker in self.markers:
marker.frequencyInput.textEdited.emit( marker.frequencyInput.textEdited.emit(marker.frequencyInput.text())
marker.frequencyInput.text())
def setReference(self, s11=None, s21=None, source=None): def setReference(self, s11=None, s21=None, source=None):
if not s11: if not s11:
@ -581,11 +621,13 @@ class NanoVNASaver(QtWidgets.QWidget):
if self.sweepSource != "": if self.sweepSource != "":
insert += ( insert += (
f"Sweep: {self.sweepSource} @ {len(self.data.s11)} points" f"Sweep: {self.sweepSource} @ {len(self.data.s11)} points"
f"{', ' if self.referenceSource else ''}") f"{', ' if self.referenceSource else ''}"
)
if self.referenceSource != "": if self.referenceSource != "":
insert += ( insert += (
f"Reference: {self.referenceSource} @" f"Reference: {self.referenceSource} @"
f" {len(self.ref_data.s11)} points") f" {len(self.ref_data.s11)} points"
)
insert += ")" insert += ")"
title = f"{self.baseTitle} {insert or ''}" title = f"{self.baseTitle} {insert or ''}"
self.setWindowTitle(title) self.setWindowTitle(title)
@ -612,7 +654,7 @@ class NanoVNASaver(QtWidgets.QWidget):
self.showError(self.worker.error_message) self.showError(self.worker.error_message)
with contextlib.suppress(IOError): with contextlib.suppress(IOError):
self.vna.flushSerialBuffers() # Remove any left-over data self.vna.flushSerialBuffers() # Remove any left-over data
self.vna.reconnect() # try reconnection self.vna.reconnect() # try reconnection
self.sweepFinished() self.sweepFinished()
def popoutChart(self, chart: Chart): def popoutChart(self, chart: Chart):
@ -661,8 +703,12 @@ class NanoVNASaver(QtWidgets.QWidget):
new_width = qf_new.horizontalAdvance(standard_string) new_width = qf_new.horizontalAdvance(standard_string)
old_width = qf_normal.horizontalAdvance(standard_string) old_width = qf_normal.horizontalAdvance(standard_string)
self.scaleFactor = new_width / old_width self.scaleFactor = new_width / old_width
logger.debug("New font width: %f, normal font: %f, factor: %f", logger.debug(
new_width, old_width, self.scaleFactor) "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 # TODO: Update all the fixed widths to account for the scaling
for m in self.markers: for m in self.markers:
m.get_data_layout().setFont(font) m.get_data_layout().setFont(font)

Wyświetl plik

@ -34,12 +34,12 @@ class Datapoint(NamedTuple):
@property @property
def z(self) -> complex: def z(self) -> complex:
""" return the s value complex number """ """return the s value complex number"""
return complex(self.re, self.im) return complex(self.re, self.im)
@property @property
def phase(self) -> float: def phase(self) -> float:
""" return the datapoint's phase value """ """return the datapoint's phase value"""
return cmath.phase(self.z) return cmath.phase(self.z)
@property @property
@ -77,11 +77,11 @@ class Datapoint(NamedTuple):
def capacitiveEquivalent(self, ref_impedance: float = 50) -> float: def capacitiveEquivalent(self, ref_impedance: float = 50) -> float:
return impedance_to_capacitance( return impedance_to_capacitance(
self.impedance(ref_impedance), self.freq) self.impedance(ref_impedance), self.freq
)
def inductiveEquivalent(self, ref_impedance: float = 50) -> float: def inductiveEquivalent(self, ref_impedance: float = 50) -> float:
return impedance_to_inductance( return impedance_to_inductance(self.impedance(ref_impedance), self.freq)
self.impedance(ref_impedance), self.freq)
def gamma_to_impedance(gamma: complex, ref_impedance: float = 50) -> complex: 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: def parallel_to_serial(z: complex) -> complex:
"""Convert parallel impedance to serial impedance equivalent""" """Convert parallel impedance to serial impedance equivalent"""
z_sq_sum = z.real ** 2 + z.imag ** 2 or 10.0e-30 z_sq_sum = z.real**2 + z.imag**2 or 10.0e-30
return complex(z.real * z.imag ** 2 / z_sq_sum, return complex(
z.real ** 2 * z.imag / z_sq_sum) 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: 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: def serial_to_parallel(z: complex) -> complex:
"""Convert serial impedance to parallel impedance equivalent""" """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: if z.real == 0 and z.imag == 0:
return complex(math.inf, math.inf) return complex(math.inf, math.inf)
if z.imag == 0: 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""" """Correct the ratio for a given attenuation on s21 input"""
if att <= 0: if att <= 0:
return data return data
att = 10**(att / 20) att = 10 ** (att / 20)
ndata = [] ndata = []
for dp in data: for dp in data:
corrected = dp.z * att corrected = dp.z * att

Wyświetl plik

@ -22,8 +22,29 @@ from decimal import Context, Decimal, InvalidOperation
from typing import NamedTuple from typing import NamedTuple
from numbers import Number, Real from numbers import Number, Real
PREFIXES = ("q", "r", "y", "z", "a", "f", "p", "n", "µ", "m", PREFIXES = (
"", "k", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q") "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: 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: def round_ceil(value: Real, digits: int = 0) -> Real:
factor = 10 ** -digits factor = 10**-digits
return factor * math.ceil(value / factor) return factor * math.ceil(value / factor)
def round_floor(value: Real, digits: int = 0) -> Real: def round_floor(value: Real, digits: int = 0) -> Real:
factor = 10 ** -digits factor = 10**-digits
return factor * math.floor(value / factor) return factor * math.floor(value / factor)
def log_floor_125(x: float) -> float: 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 log_factor = x / log_base
if log_factor >= 5: if log_factor >= 5:
return 5 * log_base return 5 * log_base
@ -80,31 +101,44 @@ class Value:
self.fmt = fmt self.fmt = fmt
if isinstance(value, str): if isinstance(value, str):
self._value = Decimal(math.nan) self._value = Decimal(math.nan)
if value.lower() != 'nan': if value.lower() != "nan":
self.parse(value) self.parse(value)
else: else:
self._value = Decimal(value, context=Value.CTX) self._value = Decimal(value, context=Value.CTX)
def __repr__(self) -> str: def __repr__(self) -> str:
return (f"{self.__class__.__name__}(" return (
f"{repr(self._value)}, '{self._unit}', {self.fmt})") f"{self.__class__.__name__}("
f"{repr(self._value)}, '{self._unit}', {self.fmt})"
)
def __str__(self) -> str: def __str__(self) -> str:
fmt = self.fmt fmt = self.fmt
if math.isnan(self._value): if math.isnan(self._value):
return f"-{fmt.space_str}{self._unit}" return f"-{fmt.space_str}{self._unit}"
if (fmt.assume_infinity and if fmt.assume_infinity and abs(self._value) >= 10 ** (
abs(self._value) >= 10 ** ((fmt.max_offset + 1) * 3)): (fmt.max_offset + 1) * 3
return (("-" if self._value < 0 else "") + ):
"\N{INFINITY}" + fmt.space_str + self._unit) return (
("-" if self._value < 0 else "")
+ "\N{INFINITY}"
+ fmt.space_str
+ self._unit
)
if self._value < fmt.printable_min: if self._value < fmt.printable_min:
return fmt.unprintable_under + self._unit return fmt.unprintable_under + self._unit
if self._value > fmt.printable_max: if self._value > fmt.printable_max:
return fmt.unprintable_over + self._unit return fmt.unprintable_over + self._unit
offset = clamp_value( offset = (
int(math.log10(abs(self._value)) // 3), clamp_value(
fmt.min_offset, fmt.max_offset) if self._value else 0 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)) real = float(self._value) / (10 ** (offset * 3))
@ -112,8 +146,9 @@ class Value:
formstr = ".0f" formstr = ".0f"
else: else:
max_digits = fmt.max_nr_digits + ( 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) < 10 else 0)
(1 if not fmt.fix_decimals and abs(real) < 100 else 0)) + (1 if not fmt.fix_decimals and abs(real) < 100 else 0)
)
formstr = f".{max_digits - 3}f" formstr = f".{max_digits - 3}f"
if self.fmt.allways_signed: if self.fmt.allways_signed:
@ -150,10 +185,13 @@ class Value:
value = value.replace(" ", "") # Ignore spaces value = value.replace(" ", "") # Ignore spaces
if self._unit and ( if self._unit and (
value.endswith(self._unit) or value.endswith(self._unit)
(self.fmt.parse_sloppy_unit and or (
value.lower().endswith(self._unit.lower()))): # strip unit self.fmt.parse_sloppy_unit
value = value[:-len(self._unit)] and value.lower().endswith(self._unit.lower())
)
): # strip unit
value = value[: -len(self._unit)]
factor = 1 factor = 1
# fix for e.g. KHz, mHz gHz as milli-Hertz mostly makes no # fix for e.g. KHz, mHz gHz as milli-Hertz mostly makes no
@ -170,13 +208,14 @@ class Value:
self._value = -math.inf self._value = -math.inf
else: else:
try: try:
self._value = (Decimal(value, context=Value.CTX) self._value = Decimal(value, context=Value.CTX) * Decimal(
* Decimal(factor, context=Value.CTX)) factor, context=Value.CTX
)
except InvalidOperation as exc: except InvalidOperation as exc:
raise ValueError() from exc raise ValueError() from exc
self._value = clamp_value(self._value, self._value = clamp_value(
self.fmt.parse_clamp_min, self._value, self.fmt.parse_clamp_min, self.fmt.parse_clamp_max
self.fmt.parse_clamp_max) )
return self return self
@property @property

Wyświetl plik

@ -57,9 +57,12 @@ class BandsModel(QtCore.QAbstractTableModel):
# These bands correspond broadly to the Danish Amateur Radio allocation # These bands correspond broadly to the Danish Amateur Radio allocation
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat, self.settings = QtCore.QSettings(
QtCore.QSettings.UserScope, QtCore.QSettings.IniFormat,
"NanoVNASaver", "Bands") QtCore.QSettings.UserScope,
"NanoVNASaver",
"Bands",
)
self.settings.setIniCodec("UTF-8") self.settings.setIniCodec("UTF-8")
self.enabled = self.settings.value("ShowBands", False, bool) self.enabled = self.settings.value("ShowBands", False, bool)
@ -71,7 +74,8 @@ class BandsModel(QtCore.QAbstractTableModel):
def saveSettings(self): def saveSettings(self):
self.settings.setValue( self.settings.setValue(
"bands", "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() self.settings.sync()
def resetBands(self): def resetBands(self):
@ -87,18 +91,22 @@ class BandsModel(QtCore.QAbstractTableModel):
def data(self, index: QModelIndex, role: int = ...) -> QtCore.QVariant: def data(self, index: QModelIndex, role: int = ...) -> QtCore.QVariant:
if role in [ 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()]) return QtCore.QVariant(self.bands[index.row()][index.column()])
if role == QtCore.Qt.TextAlignmentRole: if role == QtCore.Qt.TextAlignmentRole:
if index.column() == 0: if index.column() == 0:
return QtCore.QVariant(QtCore.Qt.AlignCenter) return QtCore.QVariant(QtCore.Qt.AlignCenter)
return QtCore.QVariant( return QtCore.QVariant(
QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter
)
return QtCore.QVariant() return QtCore.QVariant()
def setData(self, index: QModelIndex, def setData(
value: typing.Any, role: int = ...) -> bool: self, index: QModelIndex, value: typing.Any, role: int = ...
) -> bool:
if role == QtCore.Qt.EditRole and index.isValid(): if role == QtCore.Qt.EditRole and index.isValid():
t = self.bands[index.row()] t = self.bands[index.row()]
name = t[0] name = t[0]
@ -116,14 +124,14 @@ class BandsModel(QtCore.QAbstractTableModel):
return True return True
return False return False
def index(self, row: int, def index(self, row: int, column: int, _: QModelIndex = ...) -> QModelIndex:
column: int, _: QModelIndex = ...) -> QModelIndex:
return self.createIndex(row, column) return self.createIndex(row, column)
def addRow(self): def addRow(self):
self.bands.append(("New", 0, 0)) self.bands.append(("New", 0, 0))
self.dataChanged.emit(self.index(len(self.bands), 0), self.dataChanged.emit(
self.index(len(self.bands), 2)) self.index(len(self.bands), 0), self.index(len(self.bands), 2)
)
self.layoutChanged.emit() self.layoutChanged.emit()
def removeRow(self, row: int, _: QModelIndex = ...) -> bool: def removeRow(self, row: int, _: QModelIndex = ...) -> bool:
@ -132,10 +140,13 @@ class BandsModel(QtCore.QAbstractTableModel):
self.saveSettings() self.saveSettings()
return True return True
def headerData(self, section: int, def headerData(
orientation: QtCore.Qt.Orientation, role: int = ...): self, section: int, orientation: QtCore.Qt.Orientation, role: int = ...
if (role == QtCore.Qt.DisplayRole and ):
orientation == QtCore.Qt.Horizontal): if (
role == QtCore.Qt.DisplayRole
and orientation == QtCore.Qt.Horizontal
):
with contextlib.suppress(IndexError): with contextlib.suppress(IndexError):
return _HEADER_DATA[section] return _HEADER_DATA[section]
return None return None
@ -143,9 +154,10 @@ class BandsModel(QtCore.QAbstractTableModel):
def flags(self, index: QModelIndex) -> QtCore.Qt.ItemFlags: def flags(self, index: QModelIndex) -> QtCore.Qt.ItemFlags:
if index.isValid(): if index.isValid():
return QtCore.Qt.ItemFlags( return QtCore.Qt.ItemFlags(
QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEditable
QtCore.Qt.ItemIsEnabled | | QtCore.Qt.ItemIsEnabled
QtCore.Qt.ItemIsSelectable) | QtCore.Qt.ItemIsSelectable
)
super().flags(index) super().flags(index)
def setColor(self, color): def setColor(self, color):

Wyświetl plik

@ -32,10 +32,13 @@ class SweepMode(Enum):
class Properties: class Properties:
def __init__(self, name: str = "", def __init__(
mode: 'SweepMode' = SweepMode.SINGLE, self,
averages: Tuple[int, int] = (3, 0), name: str = "",
logarithmic: bool = False): mode: "SweepMode" = SweepMode.SINGLE,
averages: Tuple[int, int] = (3, 0),
logarithmic: bool = False,
):
self.name = name self.name = name
self.mode = mode self.mode = mode
self.averages = averages self.averages = averages
@ -44,13 +47,19 @@ class Properties:
def __repr__(self): def __repr__(self):
return ( return (
f"Properties('{self.name}', {self.mode}, {self.averages}," f"Properties('{self.name}', {self.mode}, {self.averages},"
f" {self.logarithmic})") f" {self.logarithmic})"
)
class Sweep: class Sweep:
def __init__(self, start: int = 3600000, end: int = 30000000, def __init__(
points: int = 101, segments: int = 1, self,
properties: 'Properties' = Properties()): start: int = 3600000,
end: int = 30000000,
points: int = 101,
segments: int = 1,
properties: "Properties" = Properties(),
):
self.start = start self.start = start
self.end = end self.end = end
self.points = points self.points = points
@ -63,18 +72,22 @@ class Sweep:
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
f"Sweep({self.start}, {self.end}, {self.points}, {self.segments}," f"Sweep({self.start}, {self.end}, {self.points}, {self.segments},"
f" {self.properties})") f" {self.properties})"
)
def __eq__(self, other) -> bool: def __eq__(self, other) -> bool:
return (self.start == other.start and return (
self.end == other.end and self.start == other.start
self.points == other.points and and self.end == other.end
self.segments == other.segments and and self.points == other.points
self.properties == other.properties) and self.segments == other.segments
and self.properties == other.properties
)
def copy(self) -> 'Sweep': def copy(self) -> "Sweep":
return Sweep(self.start, self.end, self.points, self.segments, return Sweep(
self.properties) self.start, self.end, self.points, self.segments, self.properties
)
@property @property
def span(self) -> int: def span(self) -> int:
@ -86,11 +99,11 @@ class Sweep:
def check(self): def check(self):
if ( if (
self.segments <= 0 self.segments <= 0
or self.points <= 0 or self.points <= 0
or self.start <= 0 or self.start <= 0
or self.end <= 0 or self.end <= 0
or self.stepsize < 1 or self.stepsize < 1
): ):
raise ValueError(f"Illegal sweep settings: {self}") raise ValueError(f"Illegal sweep settings: {self}")

Wyświetl plik

@ -42,9 +42,8 @@ def truncate(values: List[List[Tuple]], count: int) -> List[List[Tuple]]:
for valueset in np.swapaxes(values, 0, 1).tolist(): for valueset in np.swapaxes(values, 0, 1).tolist():
avg = complex(*np.average(valueset, 0)) avg = complex(*np.average(valueset, 0))
truncated.append( truncated.append(
sorted(valueset, sorted(valueset, key=lambda v, a=avg: abs(a - complex(*v)))[:keep]
key=lambda v, a=avg: )
abs(a - complex(*v)))[:keep])
return np.swapaxes(truncated, 0, 1).tolist() return np.swapaxes(truncated, 0, 1).tolist()
@ -87,7 +86,8 @@ class SweepWorker(QtCore.QRunnable):
logger.info("Initializing SweepWorker") logger.info("Initializing SweepWorker")
if not self.app.vna.connected(): if not self.app.vna.connected():
logger.debug( logger.debug(
"Attempted to run without being connected to the NanoVNA") "Attempted to run without being connected to the NanoVNA"
)
self.running = False self.running = False
return return
@ -106,8 +106,9 @@ class SweepWorker(QtCore.QRunnable):
if sweep.segments > 1: if sweep.segments > 1:
start = sweep.start start = sweep.start
end = sweep.end end = sweep.end
logger.debug("Resetting NanoVNA sweep to full range: %d to %d", logger.debug(
start, end) "Resetting NanoVNA sweep to full range: %d to %d", start, end
)
self.app.vna.resetSweep(start, end) self.app.vna.resetSweep(start, end)
self.percentage = 100 self.percentage = 100
@ -117,9 +118,11 @@ class SweepWorker(QtCore.QRunnable):
def _run_loop(self) -> None: def _run_loop(self) -> None:
sweep = self.sweep sweep = self.sweep
averages = (sweep.properties.averages[0] averages = (
if sweep.properties.mode == SweepMode.AVERAGE sweep.properties.averages[0]
else 1) if sweep.properties.mode == SweepMode.AVERAGE
else 1
)
logger.info("%d averages", averages) logger.info("%d averages", averages)
while True: while True:
@ -131,7 +134,8 @@ class SweepWorker(QtCore.QRunnable):
start, stop = sweep.get_index_range(i) start, stop = sweep.get_index_range(i)
freq, values11, values21 = self.readAveragedSegment( freq, values11, values21 = self.readAveragedSegment(
start, stop, averages) start, stop, averages
)
self.percentage = (i + 1) * 100 / sweep.segments self.percentage = (i + 1) * 100 / sweep.segments
self.updateData(freq, values11, values21, i) self.updateData(freq, values11, values21, i)
if sweep.properties.mode != SweepMode.CONTINOUS or self.stopped: if sweep.properties.mode != SweepMode.CONTINOUS or self.stopped:
@ -152,14 +156,18 @@ class SweepWorker(QtCore.QRunnable):
def updateData(self, frequencies, values11, values21, index): def updateData(self, frequencies, values11, values21, index):
# Update the data from (i*101) to (i+1)*101 # Update the data from (i*101) to (i+1)*101
logger.debug( logger.debug(
"Calculating data and inserting in existing data at index %d", "Calculating data and inserting in existing data at index %d", index
index) )
offset = self.sweep.points * index offset = self.sweep.points * index
raw_data11 = [Datapoint(freq, values11[i][0], values11[i][1]) raw_data11 = [
for i, freq in enumerate(frequencies)] Datapoint(freq, values11[i][0], values11[i][1])
raw_data21 = [Datapoint(freq, values21[i][0], values21[i][1]) for i, freq in enumerate(frequencies)
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) data11, data21 = self.applyCalibration(raw_data11, raw_data21)
logger.debug("update Freqs: %s, Offset: %s", len(frequencies), offset) 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.rawData11[offset + i] = raw_data11[i]
self.rawData21[offset + i] = raw_data21[i] self.rawData21[offset + i] = raw_data21[i]
logger.debug("Saving data to application (%d and %d points)", logger.debug(
len(self.data11), len(self.data21)) "Saving data to application (%d and %d points)",
len(self.data11),
len(self.data21),
)
self.app.saveData(self.data11, self.data21) self.app.saveData(self.data11, self.data21)
logger.debug('Sending "updated" signal') logger.debug('Sending "updated" signal')
self.signals.updated.emit() self.signals.updated.emit()
def applyCalibration(self, def applyCalibration(
raw_data11: List[Datapoint], self, raw_data11: List[Datapoint], raw_data21: List[Datapoint]
raw_data21: List[Datapoint] ) -> Tuple[List[Datapoint], List[Datapoint]]:
) -> Tuple[List[Datapoint], List[Datapoint]]:
data11: List[Datapoint] = [] data11: List[Datapoint] = []
data21: List[Datapoint] = [] data21: List[Datapoint] = []
@ -186,8 +196,9 @@ class SweepWorker(QtCore.QRunnable):
data11 = raw_data11.copy() data11 = raw_data11.copy()
data21 = raw_data21.copy() data21 = raw_data21.copy()
elif self.app.calibration.isValid1Port(): elif self.app.calibration.isValid1Port():
data11.extend(self.app.calibration.correct11(dp) data11.extend(
for dp in raw_data11) self.app.calibration.correct11(dp) for dp in raw_data11
)
else: else:
data11 = raw_data11.copy() data11 = raw_data11.copy()
@ -199,8 +210,10 @@ class SweepWorker(QtCore.QRunnable):
data21 = raw_data21 data21 = raw_data21
if self.offsetDelay != 0: if self.offsetDelay != 0:
data11 = [correct_delay(dp, self.offsetDelay, reflect=True) data11 = [
for dp in data11] correct_delay(dp, self.offsetDelay, reflect=True)
for dp in data11
]
data21 = [correct_delay(dp, self.offsetDelay) for dp in data21] data21 = [correct_delay(dp, self.offsetDelay) for dp in data21]
return data11, data21 return data11, data21
@ -209,8 +222,9 @@ class SweepWorker(QtCore.QRunnable):
values11 = [] values11 = []
values21 = [] values21 = []
freq = [] freq = []
logger.info("Reading from %d to %d. Averaging %d values", logger.info(
start, stop, averages) "Reading from %d to %d. Averaging %d values", start, stop, averages
)
for i in range(averages): for i in range(averages):
if self.stopped: if self.stopped:
logger.debug("Stopping averaging as signalled.") logger.debug("Stopping averaging as signalled.")
@ -227,8 +241,9 @@ class SweepWorker(QtCore.QRunnable):
retry += 1 retry += 1
freq, tmp11, tmp21 = self.readSegment(start, stop) freq, tmp11, tmp21 = self.readSegment(start, stop)
if retry > 1: if retry > 1:
logger.error("retry %s readSegment(%s,%s)", logger.error(
retry, start, stop) "retry %s readSegment(%s,%s)", retry, start, stop
)
sleep(0.5) sleep(0.5)
values11.append(tmp11) values11.append(tmp11)
values21.append(tmp21) values21.append(tmp21)
@ -240,8 +255,7 @@ class SweepWorker(QtCore.QRunnable):
truncates = self.sweep.properties.averages[1] truncates = self.sweep.properties.averages[1]
if truncates > 0 and averages > 1: if truncates > 0 and averages > 1:
logger.debug("Truncating %d values by %d", logger.debug("Truncating %d values by %d", len(values11), truncates)
len(values11), truncates)
values11 = truncate(values11, truncates) values11 = truncate(values11, truncates)
values21 = truncate(values21, truncates) values21 = truncate(values21, truncates)
@ -278,36 +292,42 @@ class SweepWorker(QtCore.QRunnable):
a, b = d.split(" ") a, b = d.split(" ")
try: try:
if self.app.vna.validateInput and ( if self.app.vna.validateInput and (
abs(float(a)) > 9.5 or abs(float(a)) > 9.5 or abs(float(b)) > 9.5
abs(float(b)) > 9.5): ):
logger.warning( logger.warning(
"Got a non plausible data value: (%s)", d) "Got a non plausible data value: (%s)", d
)
done = False done = False
break break
returndata.append((float(a), float(b))) returndata.append((float(a), float(b)))
except ValueError as exc: except ValueError as exc:
logger.exception("An exception occurred reading %s: %s", logger.exception(
data, exc) "An exception occurred reading %s: %s", data, exc
)
done = False done = False
if not done: if not done:
logger.debug("Re-reading %s", data) logger.debug("Re-reading %s", data)
sleep(0.2) sleep(0.2)
count += 1 count += 1
if count == 5: if count == 5:
logger.error("Tried and failed to read %s %d times.", logger.error(
data, count) "Tried and failed to read %s %d times.", data, count
)
logger.debug("trying to reconnect") logger.debug("trying to reconnect")
self.app.vna.reconnect() self.app.vna.reconnect()
if count >= 10: if count >= 10:
logger.critical( logger.critical(
"Tried and failed to read %s %d times. Giving up.", "Tried and failed to read %s %d times. Giving up.",
data, count) data,
count,
)
raise IOError( raise IOError(
f"Failed reading {data} {count} times.\n" f"Failed reading {data} {count} times.\n"
f"Data outside expected valid ranges," f"Data outside expected valid ranges,"
f" or in an unexpected format.\n\n" f" or in an unexpected format.\n\n"
f"You can disable data validation on the" f"You can disable data validation on the"
f"device settings screen.") f"device settings screen."
)
return returndata return returndata
def gui_error(self, message: str): def gui_error(self, message: str):

Wyświetl plik

@ -35,20 +35,22 @@ class Options:
# Fun fact: In Touchstone 1.1 spec all params are optional unordered. # Fun fact: In Touchstone 1.1 spec all params are optional unordered.
# Just the line has to start with "#" # Just the line has to start with "#"
UNIT_TO_FACTOR = { UNIT_TO_FACTOR = {
"ghz": 10 ** 9, "ghz": 10**9,
"mhz": 10 ** 6, "mhz": 10**6,
"khz": 10 ** 3, "khz": 10**3,
"hz": 10 ** 0, "hz": 10**0,
} }
VALID_UNITS = UNIT_TO_FACTOR.keys() VALID_UNITS = UNIT_TO_FACTOR.keys()
VALID_PARAMETERS = "syzgh" VALID_PARAMETERS = "syzgh"
VALID_FORMATS = ("ma", "db", "ri") VALID_FORMATS = ("ma", "db", "ri")
def __init__(self, def __init__(
unit: str = "GHZ", self,
parameter: str = "S", unit: str = "GHZ",
t_format: str = "ma", parameter: str = "S",
resistance: int = 50): t_format: str = "ma",
resistance: int = 50,
):
# set defaults # set defaults
assert unit.lower() in Options.VALID_UNITS assert unit.lower() in Options.VALID_UNITS
assert parameter.lower() in Options.VALID_PARAMETERS assert parameter.lower() in Options.VALID_PARAMETERS
@ -145,9 +147,11 @@ class Touchstone:
return self.sdata[Touchstone.FIELD_ORDER.index(name)] return self.sdata[Touchstone.FIELD_ORDER.index(name)]
def s_freq(self, name: str, freq: int) -> Datapoint: def s_freq(self, name: str, freq: int) -> Datapoint:
return Datapoint(freq, return Datapoint(
float(self._interp[name]["real"](freq)), freq,
float(self._interp[name]["imag"](freq))) float(self._interp[name]["real"](freq)),
float(self._interp[name]["imag"](freq)),
)
def swap(self): def swap(self):
self.sdata = [self.s22, self.s12, self.s21, self.s11] self.sdata = [self.s22, self.s12, self.s21, self.s11]
@ -170,12 +174,20 @@ class Touchstone:
imag.append(dp.im) imag.append(dp.im)
self._interp[i] = { self._interp[i] = {
"real": interp1d(freq, real, "real": interp1d(
kind="slinear", bounds_error=False, freq,
fill_value=(real[0], real[-1])), real,
"imag": interp1d(freq, imag, kind="slinear",
kind="slinear", bounds_error=False, bounds_error=False,
fill_value=(imag[0], imag[-1])), 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: def _parse_comments(self, fp) -> str:
@ -192,27 +204,29 @@ class Touchstone:
vals = iter(data) vals = iter(data)
for v in vals: for v in vals:
if self.opts.format == "ri": if self.opts.format == "ri":
next(data_list).append(Datapoint(freq, float(v), next(data_list).append(
float(next(vals)))) Datapoint(freq, float(v), float(next(vals)))
)
if self.opts.format == "ma": if self.opts.format == "ma":
z = cmath.rect(float(v), math.radians(float(next(vals)))) z = cmath.rect(float(v), math.radians(float(next(vals))))
next(data_list).append(Datapoint(freq, z.real, z.imag)) next(data_list).append(Datapoint(freq, z.real, z.imag))
if self.opts.format == "db": if self.opts.format == "db":
z = cmath.rect(10 ** (float(v) / 20), z = cmath.rect(
math.radians(float(next(vals)))) 10 ** (float(v) / 20), math.radians(float(next(vals)))
)
next(data_list).append(Datapoint(freq, z.real, z.imag)) next(data_list).append(Datapoint(freq, z.real, z.imag))
def load(self): def load(self):
logger.info("Attempting to open file %s", self.filename) logger.info("Attempting to open file %s", self.filename)
try: try:
with open(self.filename, encoding='utf-8') as infile: with open(self.filename, encoding="utf-8") as infile:
self.loads(infile.read()) self.loads(infile.read())
except IOError as e: except IOError as e:
logger.exception("Failed to open %s: %s", self.filename, e) logger.exception("Failed to open %s: %s", self.filename, e)
def loads(self, s: str): def loads(self, s: str):
"""Parse touchstone 1.1 string input """Parse touchstone 1.1 string input
appends to existing sdata if Touchstone object exists appends to existing sdata if Touchstone object exists
""" """
try: try:
self._loads(s) self._loads(s)
@ -239,7 +253,7 @@ class Touchstone:
continue continue
# ignore comments at data end # ignore comments at data end
data = line.split('!')[0] data = line.split("!")[0]
data = data.split() data = data.split()
freq, data = round(float(data[0]) * self.opts.factor), data[1:] freq, data = round(float(data[0]) * self.opts.factor), data[1:]
data_len = len(data) data_len = len(data)
@ -270,8 +284,7 @@ class Touchstone:
nr_params: Number of s-parameters. 2 for s1p, 4 for s2p nr_params: Number of s-parameters. 2 for s1p, 4 for s2p
""" """
logger.info("Attempting to open file %s for writing", logger.info("Attempting to open file %s for writing", self.filename)
self.filename)
with open(self.filename, "w", encoding="utf-8") as outfile: with open(self.filename, "w", encoding="utf-8") as outfile:
outfile.write(self.saves(nr_params)) outfile.write(self.saves(nr_params))

Wyświetl plik

@ -22,13 +22,16 @@ logger = logging.getLogger(__name__)
class Version: class Version:
RXP = re.compile(r"""^ RXP = re.compile(
r"""^
\D* \D*
(?P<major>\d+)\. (?P<major>\d+)\.
(?P<minor>\d+)\.? (?P<minor>\d+)\.?
(?P<revision>\d+)? (?P<revision>\d+)?
(?P<note>.*) (?P<note>.*)
$""", re.VERBOSE) $""",
re.VERBOSE,
)
def __init__(self, vstring: str = "0.0.0"): def __init__(self, vstring: str = "0.0.0"):
self.data = { self.data = {
@ -68,8 +71,10 @@ class Version:
return self.data == other.data return self.data == other.data
def __str__(self) -> str: def __str__(self) -> str:
return (f'{self.data["major"]}.{self.data["minor"]}' return (
f'.{self.data["revision"]}{self.data["note"]}') f'{self.data["major"]}.{self.data["minor"]}'
f'.{self.data["revision"]}{self.data["note"]}'
)
@property @property
def major(self) -> int: def major(self) -> int:

Wyświetl plik

@ -53,28 +53,36 @@ class AboutWindow(QtWidgets.QWidget):
layout = QtWidgets.QVBoxLayout() layout = QtWidgets.QVBoxLayout()
top_layout.addLayout(layout) top_layout.addLayout(layout)
layout.addWidget(QtWidgets.QLabel( layout.addWidget(
f"NanoVNASaver version {self.app.version}")) QtWidgets.QLabel(f"NanoVNASaver version {self.app.version}")
)
layout.addWidget(QtWidgets.QLabel("")) layout.addWidget(QtWidgets.QLabel(""))
layout.addWidget(QtWidgets.QLabel( layout.addWidget(
"\N{COPYRIGHT SIGN} Copyright 2019, 2020 Rune B. Broberg\n" QtWidgets.QLabel(
"\N{COPYRIGHT SIGN} Copyright 2020ff NanoVNA-Saver Authors" "\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( layout.addWidget(
"This program is licensed under the" QtWidgets.QLabel("This program comes with ABSOLUTELY NO WARRANTY")
" GNU General Public License version 3")) )
layout.addWidget(
QtWidgets.QLabel(
"This program is licensed under the"
" GNU General Public License version 3"
)
)
layout.addWidget(QtWidgets.QLabel("")) layout.addWidget(QtWidgets.QLabel(""))
link_label = QtWidgets.QLabel( link_label = QtWidgets.QLabel(
f'For further details, see: <a href="{INFO_URL}">' f'For further details, see: <a href="{INFO_URL}">' f"{INFO_URL}"
f"{INFO_URL}") )
link_label.setOpenExternalLinks(True) link_label.setOpenExternalLinks(True)
layout.addWidget(link_label) layout.addWidget(link_label)
layout.addWidget(QtWidgets.QLabel("")) layout.addWidget(QtWidgets.QLabel(""))
self.versionLabel = QtWidgets.QLabel( self.versionLabel = QtWidgets.QLabel(
"NanoVNA Firmware Version: Not connected.") "NanoVNA Firmware Version: Not connected."
)
layout.addWidget(self.versionLabel) layout.addWidget(self.versionLabel)
layout.addStretch() layout.addStretch()
@ -106,14 +114,15 @@ class AboutWindow(QtWidgets.QWidget):
with contextlib.suppress(IOError, AttributeError): with contextlib.suppress(IOError, AttributeError):
self.versionLabel.setText( self.versionLabel.setText(
f"NanoVNA Firmware Version: {self.app.vna.name} " 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): def findUpdates(self, automatic=False):
latest_version = Version() latest_version = Version()
latest_url = "" latest_url = ""
try: try:
req = request.Request(VERSION_URL) 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): for line in request.urlopen(req, timeout=3):
line = line.decode("utf-8") line = line.decode("utf-8")
if line.startswith("VERSION ="): if line.startswith("VERSION ="):
@ -122,17 +131,20 @@ class AboutWindow(QtWidgets.QWidget):
latest_url = line[13:].strip(" \"'") latest_url = line[13:].strip(" \"'")
except error.HTTPError as e: except error.HTTPError as e:
logger.exception( 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.") self.updateLabel.setText("Connection error.")
return return
except TypeError as e: except TypeError as e:
logger.exception( 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.") self.updateLabel.setText("Data error reading versions.")
return return
except error.URLError as e: except error.URLError as e:
logger.exception( 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.") self.updateLabel.setText("Connection error.")
return return
@ -147,13 +159,17 @@ class AboutWindow(QtWidgets.QWidget):
"Updates available", "Updates available",
f"There is a new update for NanoVNA-Saver available!\n" f"There is a new update for NanoVNA-Saver available!\n"
f"Version {latest_version}\n\n" f"Version {latest_version}\n\n"
f'Press "About" to find the update.') f'Press "About" to find the update.',
)
else: else:
QtWidgets.QMessageBox.information( QtWidgets.QMessageBox.information(
self, "Updates available", self,
"There is a new update for NanoVNA-Saver available!") "Updates available",
"There is a new update for NanoVNA-Saver available!",
)
self.updateLabel.setText( self.updateLabel.setText(
f'<a href="{latest_url}">New version available</a>.') f'<a href="{latest_url}">New version available</a>.'
)
self.updateLabel.setOpenExternalLinks(True) self.updateLabel.setOpenExternalLinks(True)
else: else:
# Probably don't show a message box, just update the screen? # Probably don't show a message box, just update the screen?
@ -161,5 +177,6 @@ class AboutWindow(QtWidgets.QWidget):
# #
self.updateLabel.setText( self.updateLabel.setText(
f"Last checked: " f"Last checked: "
f"{strftime('%Y-%m-%d %H:%M:%S', localtime())}") f"{strftime('%Y-%m-%d %H:%M:%S', localtime())}"
)
return return

Wyświetl plik

@ -29,7 +29,9 @@ from NanoVNASaver.Analysis.HighPassAnalysis import HighPassAnalysis
from NanoVNASaver.Analysis.LowPassAnalysis import LowPassAnalysis from NanoVNASaver.Analysis.LowPassAnalysis import LowPassAnalysis
from NanoVNASaver.Analysis.PeakSearchAnalysis import PeakSearchAnalysis from NanoVNASaver.Analysis.PeakSearchAnalysis import PeakSearchAnalysis
from NanoVNASaver.Analysis.ResonanceAnalysis import ResonanceAnalysis 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.Analysis.VSWRAnalysis import VSWRAnalysis
from NanoVNASaver.Windows.Defaults import make_scrollable from NanoVNASaver.Windows.Defaults import make_scrollable
@ -55,25 +57,28 @@ class AnalysisWindow(QtWidgets.QWidget):
select_analysis_box = QtWidgets.QGroupBox("Select analysis") select_analysis_box = QtWidgets.QGroupBox("Select analysis")
select_analysis_layout = QtWidgets.QFormLayout(select_analysis_box) select_analysis_layout = QtWidgets.QFormLayout(select_analysis_box)
self.analysis_list = QtWidgets.QComboBox() self.analysis_list = QtWidgets.QComboBox()
self.analysis_list.addItem("Low-pass filter", LowPassAnalysis(self.app))
self.analysis_list.addItem( self.analysis_list.addItem(
"Low-pass filter", LowPassAnalysis(self.app)) "Band-pass filter", BandPassAnalysis(self.app)
)
self.analysis_list.addItem( self.analysis_list.addItem(
"Band-pass filter", BandPassAnalysis(self.app)) "High-pass filter", HighPassAnalysis(self.app)
)
self.analysis_list.addItem( self.analysis_list.addItem(
"High-pass filter", HighPassAnalysis(self.app)) "Band-stop filter", BandStopAnalysis(self.app)
)
self.analysis_list.addItem( self.analysis_list.addItem(
"Band-stop filter", BandStopAnalysis(self.app)) "Simple Peak search", SimplePeakSearchAnalysis(self.app)
self.analysis_list.addItem( )
"Simple Peak search", SimplePeakSearchAnalysis(self.app)) self.analysis_list.addItem("Peak search", PeakSearchAnalysis(self.app))
self.analysis_list.addItem(
"Peak search", PeakSearchAnalysis(self.app))
self.analysis_list.addItem("VSWR analysis", VSWRAnalysis(self.app)) self.analysis_list.addItem("VSWR analysis", VSWRAnalysis(self.app))
self.analysis_list.addItem( 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( self.analysis_list.addItem(
"HWEF analysis", EFHWAnalysis(self.app)) "MagLoop analysis", MagLoopAnalysis(self.app)
self.analysis_list.addItem( )
"MagLoop analysis", MagLoopAnalysis(self.app))
select_analysis_layout.addRow("Analysis type", self.analysis_list) select_analysis_layout.addRow("Analysis type", self.analysis_list)
self.analysis_list.currentIndexChanged.connect(self.updateSelection) self.analysis_list.currentIndexChanged.connect(self.updateSelection)
@ -82,15 +87,18 @@ class AnalysisWindow(QtWidgets.QWidget):
select_analysis_layout.addRow(btn_run_analysis) select_analysis_layout.addRow(btn_run_analysis)
self.checkbox_run_automatically = QtWidgets.QCheckBox( self.checkbox_run_automatically = QtWidgets.QCheckBox(
"Run automatically") "Run automatically"
)
self.checkbox_run_automatically.stateChanged.connect( self.checkbox_run_automatically.stateChanged.connect(
self.toggleAutomaticRun) self.toggleAutomaticRun
)
select_analysis_layout.addRow(self.checkbox_run_automatically) select_analysis_layout.addRow(self.checkbox_run_automatically)
analysis_box = QtWidgets.QGroupBox("Analysis") analysis_box = QtWidgets.QGroupBox("Analysis")
analysis_box.setSizePolicy( analysis_box.setSizePolicy(
QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding) QtWidgets.QSizePolicy.MinimumExpanding,
)
self.analysis_layout = QtWidgets.QVBoxLayout(analysis_box) self.analysis_layout = QtWidgets.QVBoxLayout(analysis_box)
self.analysis_layout.setContentsMargins(0, 0, 0, 0) self.analysis_layout.setContentsMargins(0, 0, 0, 0)
@ -110,7 +118,8 @@ class AnalysisWindow(QtWidgets.QWidget):
if old_item is not None: if old_item is not None:
old_widget = self.analysis_layout.itemAt(0).widget() old_widget = self.analysis_layout.itemAt(0).widget()
self.analysis_layout.replaceWidget( self.analysis_layout.replaceWidget(
old_widget, self.analysis.widget()) old_widget, self.analysis.widget()
)
old_widget.hide() old_widget.hide()
else: else:
self.analysis_layout.addWidget(self.analysis.widget()) self.analysis_layout.addWidget(self.analysis.widget())

Wyświetl plik

@ -66,6 +66,7 @@ class BandsWindow(QtWidgets.QWidget):
QtWidgets.QMessageBox.Warning, QtWidgets.QMessageBox.Warning,
"Confirm reset", "Confirm reset",
"Are you sure you want to reset the bands to default?", "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: if confirm == QtWidgets.QMessageBox.Yes:
self.app.bands.resetBands() self.app.bands.resetBands()

Wyświetl plik

@ -50,8 +50,10 @@ class CalibrationWindow(QtWidgets.QWidget):
self.setMinimumWidth(450) self.setMinimumWidth(450)
self.setWindowTitle("Calibration") self.setWindowTitle("Calibration")
self.setWindowIcon(self.app.icon) self.setWindowIcon(self.app.icon)
self.setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, self.setSizePolicy(
QtWidgets.QSizePolicy.MinimumExpanding) QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding,
)
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide)
@ -67,28 +69,38 @@ class CalibrationWindow(QtWidgets.QWidget):
calibration_status_layout = QtWidgets.QFormLayout() calibration_status_layout = QtWidgets.QFormLayout()
self.calibration_status_label = QtWidgets.QLabel("Device calibration") self.calibration_status_label = QtWidgets.QLabel("Device calibration")
self.calibration_source_label = QtWidgets.QLabel("NanoVNA") self.calibration_source_label = QtWidgets.QLabel("NanoVNA")
calibration_status_layout.addRow("Calibration:", calibration_status_layout.addRow(
self.calibration_status_label) "Calibration:", self.calibration_status_label
calibration_status_layout.addRow("Source:", )
self.calibration_source_label) calibration_status_layout.addRow(
"Source:", self.calibration_source_label
)
calibration_status_group.setLayout(calibration_status_layout) calibration_status_group.setLayout(calibration_status_layout)
left_layout.addWidget(calibration_status_group) left_layout.addWidget(calibration_status_group)
calibration_control_group = QtWidgets.QGroupBox("Calibrate") calibration_control_group = QtWidgets.QGroupBox("Calibrate")
calibration_control_layout = QtWidgets.QFormLayout( calibration_control_layout = QtWidgets.QFormLayout(
calibration_control_group) calibration_control_group
)
cal_btn = {} cal_btn = {}
self.cal_label = {} self.cal_label = {}
for label_name in ("short", "open", "load", for label_name in (
"through", "thrurefl", "isolation"): "short",
"open",
"load",
"through",
"thrurefl",
"isolation",
):
self.cal_label[label_name] = QtWidgets.QLabel("Uncalibrated") self.cal_label[label_name] = QtWidgets.QLabel("Uncalibrated")
cal_btn[label_name] = QtWidgets.QPushButton( cal_btn[label_name] = QtWidgets.QPushButton(label_name.capitalize())
label_name.capitalize())
cal_btn[label_name].setMinimumHeight(20) cal_btn[label_name].setMinimumHeight(20)
cal_btn[label_name].clicked.connect( cal_btn[label_name].clicked.connect(
partial(self.manual_save, label_name)) partial(self.manual_save, label_name)
)
calibration_control_layout.addRow( 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 = QtWidgets.QDoubleSpinBox()
self.input_offset_delay.setMinimumHeight(20) self.input_offset_delay.setMinimumHeight(20)
@ -100,7 +112,8 @@ class CalibrationWindow(QtWidgets.QWidget):
calibration_control_layout.addRow(QtWidgets.QLabel("")) calibration_control_layout.addRow(QtWidgets.QLabel(""))
calibration_control_layout.addRow( 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 = QtWidgets.QPushButton("Calibration assistant")
self.btn_automatic.setMinimumHeight(20) self.btn_automatic.setMinimumHeight(20)
@ -126,7 +139,8 @@ class CalibrationWindow(QtWidgets.QWidget):
calibration_notes_group = QtWidgets.QGroupBox("Notes") calibration_notes_group = QtWidgets.QGroupBox("Notes")
calibration_notes_layout = QtWidgets.QVBoxLayout( calibration_notes_layout = QtWidgets.QVBoxLayout(
calibration_notes_group) calibration_notes_group
)
self.notes_textedit = QtWidgets.QPlainTextEdit() self.notes_textedit = QtWidgets.QPlainTextEdit()
calibration_notes_layout.addWidget(self.notes_textedit) calibration_notes_layout.addWidget(self.notes_textedit)
@ -225,7 +239,8 @@ class CalibrationWindow(QtWidgets.QWidget):
self.cal_standard_save_box = QtWidgets.QGroupBox("Saved settings") self.cal_standard_save_box = QtWidgets.QGroupBox("Saved settings")
cal_standard_save_layout = QtWidgets.QVBoxLayout( 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_box.setDisabled(True)
self.cal_standard_save_selector = QtWidgets.QComboBox() self.cal_standard_save_selector = QtWidgets.QComboBox()
@ -253,7 +268,8 @@ class CalibrationWindow(QtWidgets.QWidget):
def checkExpertUser(self): def checkExpertUser(self):
if not self.app.settings.value("ExpertCalibrationUser", False, bool): if not self.app.settings.value("ExpertCalibrationUser", False, bool):
response = QtWidgets.QMessageBox.question( response = QtWidgets.QMessageBox.question(
self, "Are you sure?", self,
"Are you sure?",
( (
"Use of the manual calibration buttons is non-intuitive," "Use of the manual calibration buttons is non-intuitive,"
" and primarily suited for users with very specialized" " and primarily suited for users with very specialized"
@ -267,7 +283,8 @@ class CalibrationWindow(QtWidgets.QWidget):
" Yes." " Yes."
), ),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel, QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel,
QtWidgets.QMessageBox.Cancel) QtWidgets.QMessageBox.Cancel,
)
if response == QtWidgets.QMessageBox.Yes: if response == QtWidgets.QMessageBox.Yes:
self.app.settings.setValue("ExpertCalibrationUser", True) self.app.settings.setValue("ExpertCalibrationUser", True)
@ -280,8 +297,7 @@ class CalibrationWindow(QtWidgets.QWidget):
self.app.calibration.insert(name, self.app.data.s21) self.app.calibration.insert(name, self.app.data.s21)
else: else:
self.app.calibration.insert(name, self.app.data.s11) self.app.calibration.insert(name, self.app.data.s11)
self.cal_label[name].setText( self.cal_label[name].setText(_format_cal_label(len(self.app.data.s11)))
_format_cal_label(len(self.app.data.s11)))
def manual_save(self, name: str): def manual_save(self, name: str):
if self.checkExpertUser(): if self.checkExpertUser():
@ -289,8 +305,7 @@ class CalibrationWindow(QtWidgets.QWidget):
def listCalibrationStandards(self): def listCalibrationStandards(self):
self.cal_standard_save_selector.clear() self.cal_standard_save_selector.clear()
num_standards = self.app.settings.beginReadArray( num_standards = self.app.settings.beginReadArray("CalibrationStandards")
"CalibrationStandards")
for i in range(num_standards): for i in range(num_standards):
self.app.settings.setArrayIndex(i) self.app.settings.setArrayIndex(i)
name = self.app.settings.value("Name", defaultValue="INVALID NAME") name = self.app.settings.value("Name", defaultValue="INVALID NAME")
@ -300,15 +315,15 @@ class CalibrationWindow(QtWidgets.QWidget):
self.cal_standard_save_selector.setCurrentText("New") self.cal_standard_save_selector.setCurrentText("New")
def saveCalibrationStandard(self): def saveCalibrationStandard(self):
num_standards = self.app.settings.beginReadArray( num_standards = self.app.settings.beginReadArray("CalibrationStandards")
"CalibrationStandards")
self.app.settings.endArray() self.app.settings.endArray()
if self.cal_standard_save_selector.currentData() == -1: if self.cal_standard_save_selector.currentData() == -1:
# New cal standard # New cal standard
# Get a name # Get a name
name, selected = QtWidgets.QInputDialog.getText( 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: if not selected or not name:
return return
write_num = num_standards write_num = num_standards
@ -317,8 +332,7 @@ class CalibrationWindow(QtWidgets.QWidget):
write_num = self.cal_standard_save_selector.currentData() write_num = self.cal_standard_save_selector.currentData()
name = self.cal_standard_save_selector.currentText() name = self.cal_standard_save_selector.currentText()
self.app.settings.beginWriteArray( self.app.settings.beginWriteArray("CalibrationStandards", num_standards)
"CalibrationStandards", num_standards)
self.app.settings.setArrayIndex(write_num) self.app.settings.setArrayIndex(write_num)
self.app.settings.setValue("Name", name) 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_l1_input.setText(str(self.app.settings.value("ShortL1", 0)))
self.short_l2_input.setText(str(self.app.settings.value("ShortL2", 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_l3_input.setText(str(self.app.settings.value("ShortL3", 0)))
self.short_length.setText( self.short_length.setText(str(self.app.settings.value("ShortDelay", 0)))
str(self.app.settings.value("ShortDelay", 0)))
self.open_c0_input.setText(str(self.app.settings.value("OpenC0", 50))) self.open_c0_input.setText(str(self.app.settings.value("OpenC0", 50)))
self.open_c1_input.setText(str(self.app.settings.value("OpenC1", 0))) 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.load_length.setText(str(self.app.settings.value("LoadDelay", 0)))
self.through_length.setText( self.through_length.setText(
str(self.app.settings.value("ThroughDelay", 0))) str(self.app.settings.value("ThroughDelay", 0))
)
self.app.settings.endArray() self.app.settings.endArray()
@ -385,8 +399,7 @@ class CalibrationWindow(QtWidgets.QWidget):
return return
delete_num = self.cal_standard_save_selector.currentData() delete_num = self.cal_standard_save_selector.currentData()
logger.debug("Deleting calibration no %d", delete_num) logger.debug("Deleting calibration no %d", delete_num)
num_standards = self.app.settings.beginReadArray( num_standards = self.app.settings.beginReadArray("CalibrationStandards")
"CalibrationStandards")
self.app.settings.endArray() self.app.settings.endArray()
logger.debug("Number of standards known: %d", num_standards) logger.debug("Number of standards known: %d", num_standards)
@ -449,7 +462,8 @@ class CalibrationWindow(QtWidgets.QWidget):
self.app.settings.endArray() self.app.settings.endArray()
self.app.settings.beginWriteArray( self.app.settings.beginWriteArray(
"CalibrationStandards", len(names)) "CalibrationStandards", len(names)
)
for i, name in enumerate(names): for i, name in enumerate(names):
self.app.settings.setArrayIndex(i) self.app.settings.setArrayIndex(i)
self.app.settings.setValue("Name", name) self.app.settings.setValue("Name", name)
@ -488,8 +502,11 @@ class CalibrationWindow(QtWidgets.QWidget):
if len(self.app.worker.rawData11) > 0: if len(self.app.worker.rawData11) > 0:
# There's raw data, so we can get corrected data # There's raw data, so we can get corrected data
logger.debug("Saving and displaying raw data.") logger.debug("Saving and displaying raw data.")
self.app.saveData(self.app.worker.rawData11, self.app.saveData(
self.app.worker.rawData21, self.app.sweepSource) self.app.worker.rawData11,
self.app.worker.rawData21,
self.app.sweepSource,
)
self.app.worker.signals.updated.emit() self.app.worker.signals.updated.emit()
def setOffsetDelay(self, value: float): def setOffsetDelay(self, value: float):
@ -498,12 +515,18 @@ class CalibrationWindow(QtWidgets.QWidget):
if len(self.app.worker.rawData11) > 0: if len(self.app.worker.rawData11) > 0:
# There's raw data, so we can get corrected data # There's raw data, so we can get corrected data
logger.debug("Applying new offset to existing sweep 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.data11,
self.app.worker.rawData11, self.app.worker.rawData21) self.app.worker.data21,
) = self.app.worker.applyCalibration(
self.app.worker.rawData11, self.app.worker.rawData21
)
logger.debug("Saving and displaying corrected data.") logger.debug("Saving and displaying corrected data.")
self.app.saveData(self.app.worker.data11, self.app.saveData(
self.app.worker.data21, self.app.sweepSource) self.app.worker.data11,
self.app.worker.data21,
self.app.sweepSource,
)
self.app.worker.signals.updated.emit() self.app.worker.signals.updated.emit()
def calculate(self): def calculate(self):
@ -511,7 +534,8 @@ class CalibrationWindow(QtWidgets.QWidget):
if self.app.sweep_control.btn_stop.isEnabled(): if self.app.sweep_control.btn_stop.isEnabled():
self.app.showError( self.app.showError(
"Unable to apply calibration while a sweep is running." "Unable to apply calibration while a sweep is running."
" Please stop the sweep and try again.") " Please stop the sweep and try again."
)
return return
cal_element.short_is_ideal = True cal_element.short_is_ideal = True
@ -528,63 +552,85 @@ class CalibrationWindow(QtWidgets.QWidget):
# We are using custom calibration standards # We are using custom calibration standards
cal_element.short_l0 = getFloatValue( cal_element.short_l0 = (
self.short_l0_input.text()) / 1.0e12 getFloatValue(self.short_l0_input.text()) / 1.0e12
cal_element.short_l1 = getFloatValue( )
self.short_l1_input.text()) / 1.0e24 cal_element.short_l1 = (
cal_element.short_l2 = getFloatValue( getFloatValue(self.short_l1_input.text()) / 1.0e24
self.short_l2_input.text()) / 1.0e33 )
cal_element.short_l3 = getFloatValue( cal_element.short_l2 = (
self.short_l3_input.text()) / 1.0e42 getFloatValue(self.short_l2_input.text()) / 1.0e33
cal_element.short_length = getFloatValue( )
self.short_length.text()) / 1.0e12 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( cal_element.open_c0 = (
self.open_c0_input.text()) / 1.e15 getFloatValue(self.open_c0_input.text()) / 1.0e15
cal_element.open_c1 = getFloatValue( )
self.open_c1_input.text()) / 1.e27 cal_element.open_c1 = (
cal_element.open_c2 = getFloatValue( getFloatValue(self.open_c1_input.text()) / 1.0e27
self.open_c2_input.text()) / 1.0e36 )
cal_element.open_c3 = getFloatValue( cal_element.open_c2 = (
self.open_c3_input.text()) / 1.0e45 getFloatValue(self.open_c2_input.text()) / 1.0e36
cal_element.openLength = getFloatValue( )
self.open_length.text()) / 1.0e12 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( cal_element.load_r = getFloatValue(self.load_resistance.text())
self.load_resistance.text()) cal_element.load_l = (
cal_element.load_l = getFloatValue( getFloatValue(self.load_inductance.text()) / 1.0e12
self.load_inductance.text()) / 1.0e12 )
cal_element.load_c = getFloatValue( cal_element.load_c = (
self.load_capacitance.text()) / 1.0e15 getFloatValue(self.load_capacitance.text()) / 1.0e15
cal_element.load_length = getFloatValue( )
self.load_length.text()) / 1.0e12 cal_element.load_length = (
getFloatValue(self.load_length.text()) / 1.0e12
)
cal_element.through_length = getFloatValue( cal_element.through_length = (
self.through_length.text()) / 1.0e12 getFloatValue(self.through_length.text()) / 1.0e12
)
logger.debug("Attempting calibration calculation.") logger.debug("Attempting calibration calculation.")
try: try:
self.app.calibration.calc_corrections() self.app.calibration.calc_corrections()
self.calibration_status_label.setText( self.calibration_status_label.setText(
_format_cal_label(self.app.calibration.size(), _format_cal_label(
"Application calibration")) self.app.calibration.size(), "Application calibration"
)
)
if self.use_ideal_values.isChecked(): if self.use_ideal_values.isChecked():
self.calibration_source_label.setText( self.calibration_source_label.setText(
self.app.calibration.source) self.app.calibration.source
)
else: else:
self.calibration_source_label.setText( self.calibration_source_label.setText(
f"{self.app.calibration.source} (Standards: Custom)") f"{self.app.calibration.source} (Standards: Custom)"
)
if self.app.worker.rawData11: if self.app.worker.rawData11:
# There's raw data, so we can get corrected data # There's raw data, so we can get corrected data
logger.debug("Applying calibration to existing sweep data.") logger.debug("Applying calibration to existing sweep data.")
self.app.worker.data11, self.app.worker.data21 = ( (
self.app.worker.applyCalibration( self.app.worker.data11,
self.app.worker.rawData11, self.app.worker.data21,
self.app.worker.rawData21)) ) = self.app.worker.applyCalibration(
self.app.worker.rawData11, self.app.worker.rawData21
)
logger.debug("Saving and displaying corrected data.") logger.debug("Saving and displaying corrected data.")
self.app.saveData(self.app.worker.data11, self.app.saveData(
self.app.worker.data21, self.app.sweepSource) self.app.worker.data11,
self.app.worker.data21,
self.app.sweepSource,
)
self.app.worker.signals.updated.emit() self.app.worker.signals.updated.emit()
except ValueError as e: except ValueError as e:
if logger.isEnabledFor(logging.DEBUG): if logger.isEnabledFor(logging.DEBUG):
@ -592,23 +638,29 @@ class CalibrationWindow(QtWidgets.QWidget):
# showError here hides the calibration window, # showError here hides the calibration window,
# so we need to pop up our own # so we need to pop up our own
QtWidgets.QMessageBox.warning( QtWidgets.QMessageBox.warning(
self, "Error applying calibration", str(e)) self, "Error applying calibration", str(e)
)
self.calibration_status_label.setText( self.calibration_status_label.setText(
"Applying calibration failed.") "Applying calibration failed."
)
self.calibration_source_label.setText(self.app.calibration.source) self.calibration_source_label.setText(self.app.calibration.source)
def loadCalibration(self): def loadCalibration(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName( filename, _ = QtWidgets.QFileDialog.getOpenFileName(
filter="Calibration Files (*.cal);;All files (*.*)") filter="Calibration Files (*.cal);;All files (*.*)"
)
if filename: if filename:
self.app.calibration.load(filename) self.app.calibration.load(filename)
if not self.app.calibration.isValid1Port(): if not self.app.calibration.isValid1Port():
return return
for i, name in enumerate( for i, name in enumerate(
("short", "open", "load", "through", "isolation", "thrurefl")): ("short", "open", "load", "through", "isolation", "thrurefl")
):
self.cal_label[name].setText( self.cal_label[name].setText(
_format_cal_label(self.app.calibration.data_size(name), _format_cal_label(
"Loaded")) self.app.calibration.data_size(name), "Loaded"
)
)
if i == 2 and not self.app.calibration.isValid2Port(): if i == 2 and not self.app.calibration.isValid2Port():
break break
self.calculate() self.calculate()
@ -633,8 +685,9 @@ class CalibrationWindow(QtWidgets.QWidget):
if not filename: if not filename:
logger.debug("No file name selected.") logger.debug("No file name selected.")
return return
self.app.calibration.notes = self.notes_textedit.toPlainText( self.app.calibration.notes = (
).splitlines() self.notes_textedit.toPlainText().splitlines()
)
try: try:
self.app.calibration.save(filename) self.app.calibration.save(filename)
self.app.settings.setValue("CalibrationFile", 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_load_box.setDisabled(self.use_ideal_values.isChecked())
self.cal_through_box.setDisabled(self.use_ideal_values.isChecked()) self.cal_through_box.setDisabled(self.use_ideal_values.isChecked())
self.cal_standard_save_box.setDisabled( self.cal_standard_save_box.setDisabled(
self.use_ideal_values.isChecked()) self.use_ideal_values.isChecked()
)
def automaticCalibration(self): def automaticCalibration(self):
self.btn_automatic.setDisabled(True) self.btn_automatic.setDisabled(True)
@ -662,14 +716,15 @@ class CalibrationWindow(QtWidgets.QWidget):
"Before starting, ensure you have Open, Short and Load" "Before starting, ensure you have Open, Short and Load"
" standards available, and the cables you wish to have" " standards available, and the cables you wish to have"
" calibrated with the device connected.<br><br>" " calibrated with the device connected.<br><br>"
"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.<br><br>" " connector to hand.<br><br>"
"<b>The best results are achieved by having the NanoVNA" "<b>The best results are achieved by having the NanoVNA"
" calibrated on-device for the full span of interest and saved" " calibrated on-device for the full span of interest and saved"
" to save slot 0 before starting.</b><br><br>" " to save slot 0 before starting.</b><br><br>"
"Once you are ready to proceed, press Ok." "Once you are ready to proceed, press Ok."
), ),
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
)
response = introduction.exec() response = introduction.exec()
if response != QtWidgets.QMessageBox.Ok: if response != QtWidgets.QMessageBox.Ok:
self.btn_automatic.setDisabled(False) self.btn_automatic.setDisabled(False)
@ -679,8 +734,10 @@ class CalibrationWindow(QtWidgets.QWidget):
QtWidgets.QMessageBox( QtWidgets.QMessageBox(
QtWidgets.QMessageBox.Information, QtWidgets.QMessageBox.Information,
"NanoVNA not connected", "NanoVNA not connected",
("Please ensure the NanoVNA is connected before attempting" (
" calibration.") "Please ensure the NanoVNA is connected before attempting"
" calibration."
),
).exec() ).exec()
self.btn_automatic.setDisabled(False) self.btn_automatic.setDisabled(False)
return return
@ -689,8 +746,10 @@ class CalibrationWindow(QtWidgets.QWidget):
QtWidgets.QMessageBox( QtWidgets.QMessageBox(
QtWidgets.QMessageBox.Information, QtWidgets.QMessageBox.Information,
"Continuous sweep enabled", "Continuous sweep enabled",
("Please disable continuous sweeping before attempting" (
" calibration.") "Please disable continuous sweeping before attempting"
" calibration."
),
).exec() ).exec()
self.btn_automatic.setDisabled(False) self.btn_automatic.setDisabled(False)
return return
@ -699,11 +758,12 @@ class CalibrationWindow(QtWidgets.QWidget):
QtWidgets.QMessageBox.Information, QtWidgets.QMessageBox.Information,
"Calibrate short", "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" " NanoVNA.\n\n"
"Press Ok when you are ready to continue." "Press Ok when you are ready to continue."
), ),
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
)
response = short_step.exec() response = short_step.exec()
if response != QtWidgets.QMessageBox.Ok: if response != QtWidgets.QMessageBox.Ok:
@ -719,7 +779,8 @@ class CalibrationWindow(QtWidgets.QWidget):
def automaticCalibrationStep(self): def automaticCalibrationStep(self):
if self.nextStep == -1: if self.nextStep == -1:
self.app.worker.signals.finished.disconnect( self.app.worker.signals.finished.disconnect(
self.automaticCalibrationStep) self.automaticCalibrationStep
)
return return
if self.nextStep == 0: if self.nextStep == 0:
@ -731,20 +792,22 @@ class CalibrationWindow(QtWidgets.QWidget):
QtWidgets.QMessageBox.Information, QtWidgets.QMessageBox.Information,
"Calibrate open", "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" " NanoVNA.\n\n"
"Either use a supplied open, or leave the end of the" "Either use a supplied open, or leave the end of the"
" cable unconnected if desired.\n\n" " cable unconnected if desired.\n\n"
"Press Ok when you are ready to continue." "Press Ok when you are ready to continue."
), ),
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
)
response = open_step.exec() response = open_step.exec()
if response != QtWidgets.QMessageBox.Ok: if response != QtWidgets.QMessageBox.Ok:
self.nextStep = -1 self.nextStep = -1
self.btn_automatic.setDisabled(False) self.btn_automatic.setDisabled(False)
self.app.worker.signals.finished.disconnect( self.app.worker.signals.finished.disconnect(
self.automaticCalibrationStep) self.automaticCalibrationStep
)
return return
self.app.sweep_start() self.app.sweep_start()
return return
@ -757,18 +820,20 @@ class CalibrationWindow(QtWidgets.QWidget):
QtWidgets.QMessageBox.Information, QtWidgets.QMessageBox.Information,
"Calibrate load", "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" " NanoVNA.\n\n"
"Press Ok when you are ready to continue." "Press Ok when you are ready to continue."
), ),
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
)
response = load_step.exec() response = load_step.exec()
if response != QtWidgets.QMessageBox.Ok: if response != QtWidgets.QMessageBox.Ok:
self.btn_automatic.setDisabled(False) self.btn_automatic.setDisabled(False)
self.nextStep = -1 self.nextStep = -1
self.app.worker.signals.finished.disconnect( self.app.worker.signals.finished.disconnect(
self.automaticCalibrationStep) self.automaticCalibrationStep
)
return return
self.app.sweep_start() self.app.sweep_start()
return return
@ -784,45 +849,51 @@ class CalibrationWindow(QtWidgets.QWidget):
"The required steps for a 1-port calibration are now" "The required steps for a 1-port calibration are now"
" complete.\n\n" " complete.\n\n"
"If you wish to continue and perform a 2-port calibration," "If you wish to continue and perform a 2-port calibration,"
" press \"Yes\". To apply the 1-port calibration and stop," ' press "Yes". To apply the 1-port calibration and stop,'
" press \"Apply\"" ' press "Apply"'
), ),
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Apply | QtWidgets.QMessageBox.Yes
QtWidgets.QMessageBox.Cancel) | QtWidgets.QMessageBox.Apply
| QtWidgets.QMessageBox.Cancel,
)
response = continue_step.exec() response = continue_step.exec()
if response == QtWidgets.QMessageBox.Apply: if response == QtWidgets.QMessageBox.Apply:
self.calculate() self.calculate()
self.nextStep = -1 self.nextStep = -1
self.app.worker.signals.finished.disconnect( self.app.worker.signals.finished.disconnect(
self.automaticCalibrationStep) self.automaticCalibrationStep
)
self.btn_automatic.setDisabled(False) self.btn_automatic.setDisabled(False)
return return
if response != QtWidgets.QMessageBox.Yes: if response != QtWidgets.QMessageBox.Yes:
self.btn_automatic.setDisabled(False) self.btn_automatic.setDisabled(False)
self.nextStep = -1 self.nextStep = -1
self.app.worker.signals.finished.disconnect( self.app.worker.signals.finished.disconnect(
self.automaticCalibrationStep) self.automaticCalibrationStep
)
return return
isolation_step = QtWidgets.QMessageBox( isolation_step = QtWidgets.QMessageBox(
QtWidgets.QMessageBox.Information, QtWidgets.QMessageBox.Information,
"Calibrate isolation", "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" " NanoVNA.\n\n"
"If available, also connect a load standard to" "If available, also connect a load standard to"
" port 0.\n\n" " port 0.\n\n"
"Press Ok when you are ready to continue." "Press Ok when you are ready to continue."
), ),
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
)
response = isolation_step.exec() response = isolation_step.exec()
if response != QtWidgets.QMessageBox.Ok: if response != QtWidgets.QMessageBox.Ok:
self.btn_automatic.setDisabled(False) self.btn_automatic.setDisabled(False)
self.nextStep = -1 self.nextStep = -1
self.app.worker.signals.finished.disconnect( self.app.worker.signals.finished.disconnect(
self.automaticCalibrationStep) self.automaticCalibrationStep
)
return return
self.app.sweep_start() self.app.sweep_start()
return return
@ -835,18 +906,20 @@ class CalibrationWindow(QtWidgets.QWidget):
QtWidgets.QMessageBox.Information, QtWidgets.QMessageBox.Information,
"Calibrate through", "Calibrate through",
( (
"Please connect the \"through\" standard between" 'Please connect the "through" standard between'
" port 0 and port 1 of the NanoVNA.\n\n" " port 0 and port 1 of the NanoVNA.\n\n"
"Press Ok when you are ready to continue." "Press Ok when you are ready to continue."
), ),
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel) QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
)
response = through_step.exec() response = through_step.exec()
if response != QtWidgets.QMessageBox.Ok: if response != QtWidgets.QMessageBox.Ok:
self.btn_automatic.setDisabled(False) self.btn_automatic.setDisabled(False)
self.nextStep = -1 self.nextStep = -1
self.app.worker.signals.finished.disconnect( self.app.worker.signals.finished.disconnect(
self.automaticCalibrationStep) self.automaticCalibrationStep
)
return return
self.app.sweep_start() self.app.sweep_start()
return return
@ -860,21 +933,24 @@ class CalibrationWindow(QtWidgets.QWidget):
"Calibrate complete", "Calibrate complete",
( (
"The calibration process is now complete. Press" "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() response = apply_step.exec()
if response != QtWidgets.QMessageBox.Apply: if response != QtWidgets.QMessageBox.Apply:
self.btn_automatic.setDisabled(False) self.btn_automatic.setDisabled(False)
self.nextStep = -1 self.nextStep = -1
self.app.worker.signals.finished.disconnect( self.app.worker.signals.finished.disconnect(
self.automaticCalibrationStep) self.automaticCalibrationStep
)
return return
self.calculate() self.calculate()
self.btn_automatic.setDisabled(False) self.btn_automatic.setDisabled(False)
self.nextStep = -1 self.nextStep = -1
self.app.worker.signals.finished.disconnect( self.app.worker.signals.finished.disconnect(
self.automaticCalibrationStep) self.automaticCalibrationStep
)
return return

Wyświetl plik

@ -23,7 +23,9 @@ from PyQt5 import QtWidgets
logger = logging.getLogger(__name__) 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 = QtWidgets.QScrollArea()
area.setWidgetResizable(True) area.setWidgetResizable(True)
outer = QtWidgets.QVBoxLayout() outer = QtWidgets.QVBoxLayout()

Wyświetl plik

@ -65,9 +65,11 @@ class DeviceSettingsWindow(QtWidgets.QWidget):
settings_layout = QtWidgets.QFormLayout(settings_box) settings_layout = QtWidgets.QFormLayout(settings_box)
self.chkValidateInputData = QtWidgets.QCheckBox( self.chkValidateInputData = QtWidgets.QCheckBox(
"Validate received data") "Validate received data"
)
validate_input = self.app.settings.value( validate_input = self.app.settings.value(
"SerialInputValidation", False, bool) "SerialInputValidation", False, bool
)
self.chkValidateInputData.setChecked(validate_input) self.chkValidateInputData.setChecked(validate_input)
self.chkValidateInputData.stateChanged.connect(self.updateValidation) self.chkValidateInputData.stateChanged.connect(self.updateValidation)
settings_layout.addRow("Validation", self.chkValidateInputData) settings_layout.addRow("Validation", self.chkValidateInputData)
@ -100,12 +102,10 @@ class DeviceSettingsWindow(QtWidgets.QWidget):
settings_layout.addRow(form_layout) settings_layout.addRow(form_layout)
def _set_datapoint_index(self, dpoints: int): def _set_datapoint_index(self, dpoints: int):
self.datapoints.setCurrentIndex( self.datapoints.setCurrentIndex(self.datapoints.findText(str(dpoints)))
self.datapoints.findText(str(dpoints)))
def _set_bandwidth_index(self, bw: int): def _set_bandwidth_index(self, bw: int):
self.bandwidth.setCurrentIndex( self.bandwidth.setCurrentIndex(self.bandwidth.findText(str(bw)))
self.bandwidth.findText(str(bw)))
def show(self): def show(self):
super().show() super().show()
@ -120,10 +120,10 @@ class DeviceSettingsWindow(QtWidgets.QWidget):
self.btnCaptureScreenshot.setDisabled(True) self.btnCaptureScreenshot.setDisabled(True)
return return
self.label["status"].setText( self.label["status"].setText(f"Connected to {self.app.vna.name}.")
f"Connected to {self.app.vna.name}.")
self.label["firmware"].setText( 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: if self.app.worker.running:
self.label["calibration"].setText("(Sweep running)") self.label["calibration"].setText("(Sweep running)")
else: else:

Wyświetl plik

@ -22,8 +22,7 @@ from typing import List
from PyQt5 import QtWidgets, QtCore, QtGui from PyQt5 import QtWidgets, QtCore, QtGui
from NanoVNASaver import Defaults from NanoVNASaver import Defaults
from NanoVNASaver.Charts.Chart import ( from NanoVNASaver.Charts.Chart import Chart, ChartColors
Chart, ChartColors)
from NanoVNASaver.Windows.Bands import BandsWindow from NanoVNASaver.Windows.Bands import BandsWindow
from NanoVNASaver.Windows.Defaults import make_scrollable from NanoVNASaver.Windows.Defaults import make_scrollable
from NanoVNASaver.Windows.MarkerSettings import MarkerSettingsWindow from NanoVNASaver.Windows.MarkerSettings import MarkerSettingsWindow
@ -60,20 +59,24 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
self.returnloss_group.addButton(self.returnloss_is_negative) self.returnloss_group.addButton(self.returnloss_is_negative)
display_options_layout.addRow( 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) display_options_layout.addRow("", self.returnloss_is_positive)
self.returnloss_is_positive.setChecked( self.returnloss_is_positive.setChecked(
Defaults.cfg.chart.returnloss_is_positive) Defaults.cfg.chart.returnloss_is_positive
)
self.returnloss_is_negative.setChecked( 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.returnloss_is_positive.toggled.connect(self.changeReturnLoss)
self.changeReturnLoss() self.changeReturnLoss()
self.show_lines_option = QtWidgets.QCheckBox("Show lines") self.show_lines_option = QtWidgets.QCheckBox("Show lines")
show_lines_label = QtWidgets.QLabel( 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) self.show_lines_option.stateChanged.connect(self.changeShowLines)
display_options_layout.addRow(self.show_lines_option, show_lines_label) 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.setSuffix(" px")
self.lineThicknessInput.setAlignment(QtCore.Qt.AlignRight) self.lineThicknessInput.setAlignment(QtCore.Qt.AlignRight)
self.lineThicknessInput.valueChanged.connect(self.changeLineThickness) self.lineThicknessInput.valueChanged.connect(self.changeLineThickness)
display_options_layout.addRow( display_options_layout.addRow("Line thickness", self.lineThicknessInput)
"Line thickness", self.lineThicknessInput)
self.markerSizeInput = QtWidgets.QSpinBox() self.markerSizeInput = QtWidgets.QSpinBox()
self.markerSizeInput.setMinimumHeight(20) self.markerSizeInput.setMinimumHeight(20)
@ -122,25 +124,31 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
display_options_layout.addRow("Marker size", self.markerSizeInput) display_options_layout.addRow("Marker size", self.markerSizeInput)
self.show_marker_number_option = QtWidgets.QCheckBox( self.show_marker_number_option = QtWidgets.QCheckBox(
"Show marker numbers") "Show marker numbers"
)
show_marker_number_label = QtWidgets.QLabel( 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.show_marker_number_option.stateChanged.connect(
self.changeShowMarkerNumber) self.changeShowMarkerNumber
)
display_options_layout.addRow( 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") self.filled_marker_option = QtWidgets.QCheckBox("Filled markers")
filled_marker_label = QtWidgets.QLabel( filled_marker_label = QtWidgets.QLabel(
"Shows the marker as a filled triangle") "Shows the marker as a filled triangle"
self.filled_marker_option.stateChanged.connect( )
self.changeFilledMarkers) self.filled_marker_option.stateChanged.connect(self.changeFilledMarkers)
display_options_layout.addRow( 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_tip_group = QtWidgets.QButtonGroup()
self.marker_at_center = QtWidgets.QRadioButton( 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_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_center)
self.marker_tip_group.addButton(self.marker_at_tip) 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 = QtWidgets.QCheckBox("Show bands")
self.show_bands.setChecked(self.app.bands.enabled) self.show_bands.setChecked(self.app.bands.enabled)
self.show_bands.stateChanged.connect( 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(self.show_bands)
bands_layout.addRow( bands_layout.addRow(
"Chart bands", "Chart bands", self.color_picker("BandsColor", "bands")
self.color_picker("BandsColor", "bands")) )
self.btn_manage_bands = QtWidgets.QPushButton("Manage bands") self.btn_manage_bands = QtWidgets.QPushButton("Manage bands")
self.btn_manage_bands.setMinimumHeight(20) self.btn_manage_bands.setMinimumHeight(20)
@ -201,16 +210,19 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
vswr_marker_layout = QtWidgets.QFormLayout(vswr_marker_box) vswr_marker_layout = QtWidgets.QFormLayout(vswr_marker_box)
self.vswrMarkers: List[float] = self.app.settings.value( self.vswrMarkers: List[float] = self.app.settings.value(
"VSWRMarkers", [], float) "VSWRMarkers", [], float
)
if isinstance(self.vswrMarkers, float): if isinstance(self.vswrMarkers, float):
# Single values from the .ini become floats rather than lists. # Single values from the .ini become floats rather than lists.
# Convert them. # 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_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 = QtWidgets.QComboBox()
self.vswr_marker_dropdown.setMinimumHeight(20) self.vswr_marker_dropdown.setMinimumHeight(20)
@ -281,7 +293,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
else: else:
chart00_selection.setCurrentText("S11 Smith Chart") chart00_selection.setCurrentText("S11 Smith Chart")
chart00_selection.currentTextChanged.connect( 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) charts_layout.addWidget(chart00_selection, 0, 0)
chart01_selection = QtWidgets.QComboBox() chart01_selection = QtWidgets.QComboBox()
@ -293,7 +306,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
else: else:
chart01_selection.setCurrentText("S11 Return Loss") chart01_selection.setCurrentText("S11 Return Loss")
chart01_selection.currentTextChanged.connect( 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) charts_layout.addWidget(chart01_selection, 0, 1)
chart02_selection = QtWidgets.QComboBox() chart02_selection = QtWidgets.QComboBox()
@ -305,7 +319,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
else: else:
chart02_selection.setCurrentText("None") chart02_selection.setCurrentText("None")
chart02_selection.currentTextChanged.connect( 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) charts_layout.addWidget(chart02_selection, 0, 2)
chart10_selection = QtWidgets.QComboBox() chart10_selection = QtWidgets.QComboBox()
@ -317,7 +332,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
else: else:
chart10_selection.setCurrentText("S21 Polar Plot") chart10_selection.setCurrentText("S21 Polar Plot")
chart10_selection.currentTextChanged.connect( 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) charts_layout.addWidget(chart10_selection, 1, 0)
chart11_selection = QtWidgets.QComboBox() chart11_selection = QtWidgets.QComboBox()
@ -329,7 +345,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
else: else:
chart11_selection.setCurrentText("S21 Gain") chart11_selection.setCurrentText("S21 Gain")
chart11_selection.currentTextChanged.connect( 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) charts_layout.addWidget(chart11_selection, 1, 1)
chart12_selection = QtWidgets.QComboBox() chart12_selection = QtWidgets.QComboBox()
@ -341,7 +358,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
else: else:
chart12_selection.setCurrentText("None") chart12_selection.setCurrentText("None")
chart12_selection.currentTextChanged.connect( 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) charts_layout.addWidget(chart12_selection, 1, 2)
self.changeChart(0, 0, chart00_selection.currentText()) self.changeChart(0, 0, chart00_selection.currentText())
@ -353,30 +371,36 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
chart_colors = ChartColors() chart_colors = ChartColors()
Chart.color.background = self.app.settings.value( Chart.color.background = self.app.settings.value(
"BackgroundColor", defaultValue=chart_colors.background, "BackgroundColor",
type=QtGui.QColor) defaultValue=chart_colors.background,
type=QtGui.QColor,
)
Chart.color.foreground = self.app.settings.value( Chart.color.foreground = self.app.settings.value(
"ForegroundColor", defaultValue=chart_colors.foreground, "ForegroundColor",
type=QtGui.QColor) defaultValue=chart_colors.foreground,
type=QtGui.QColor,
)
Chart.color.text = self.app.settings.value( Chart.color.text = self.app.settings.value(
"TextColor", defaultValue=chart_colors.text, "TextColor", defaultValue=chart_colors.text, type=QtGui.QColor
type=QtGui.QColor) )
self.bandsColor = self.app.settings.value( self.bandsColor = self.app.settings.value(
"BandsColor", defaultValue=chart_colors.bands, "BandsColor", defaultValue=chart_colors.bands, type=QtGui.QColor
type=QtGui.QColor) )
self.app.bands.color = Chart.color.bands self.app.bands.color = Chart.color.bands
Chart.color.swr = self.app.settings.value( Chart.color.swr = self.app.settings.value(
"VSWRColor", defaultValue=chart_colors.swr, "VSWRColor", defaultValue=chart_colors.swr, type=QtGui.QColor
type=QtGui.QColor) )
self.dark_mode_option.setChecked(Defaults.cfg.gui.dark_mode) self.dark_mode_option.setChecked(Defaults.cfg.gui.dark_mode)
self.show_lines_option.setChecked(Defaults.cfg.chart.show_lines) self.show_lines_option.setChecked(Defaults.cfg.chart.show_lines)
self.show_marker_number_option.setChecked( 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) self.filled_marker_option.setChecked(Defaults.cfg.chart.marker_filled)
if self.app.settings.value("UseCustomColors", if self.app.settings.value(
defaultValue=False, type=bool): "UseCustomColors", defaultValue=False, type=bool
):
self.dark_mode_option.setDisabled(True) self.dark_mode_option.setDisabled(True)
self.dark_mode_option.setChecked(False) self.dark_mode_option.setChecked(False)
self.use_custom_colors.setChecked(True) self.use_custom_colors.setChecked(True)
@ -395,20 +419,23 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def trace_colors(self, layout: QtWidgets.QLayout): def trace_colors(self, layout: QtWidgets.QLayout):
for setting, name, attr in ( for setting, name, attr in (
('SweepColor', 'Sweep color', 'sweep'), ("SweepColor", "Sweep color", "sweep"),
('SecondarySweepColor', 'Second sweep color', 'sweep_secondary'), ("SecondarySweepColor", "Second sweep color", "sweep_secondary"),
('ReferenceColor', 'Reference color', 'reference'), ("ReferenceColor", "Reference color", "reference"),
('SecondaryReferenceColor', (
'Second reference color', 'reference_secondary'), "SecondaryReferenceColor",
"Second reference color",
"reference_secondary",
),
): ):
cp = self.color_picker(setting, attr) cp = self.color_picker(setting, attr)
layout.addRow(name, cp) layout.addRow(name, cp)
def custom_colors(self, layout: QtWidgets.QLayout): def custom_colors(self, layout: QtWidgets.QLayout):
for setting, name, attr in ( for setting, name, attr in (
('BackgroundColor', 'Chart background', 'background'), ("BackgroundColor", "Chart background", "background"),
('ForegroundColor', 'Chart foreground', 'foreground'), ("ForegroundColor", "Chart foreground", "foreground"),
('TextColor', 'Chart text', 'text'), ("TextColor", "Chart text", "text"),
): ):
cp = self.color_picker(setting, attr) cp = self.color_picker(setting, attr)
layout.addRow(name, cp) layout.addRow(name, cp)
@ -419,7 +446,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
cp.setMinimumHeight(20) cp.setMinimumHeight(20)
default = getattr(Chart.color, attr) default = getattr(Chart.color, attr)
color = self.app.settings.value( color = self.app.settings.value(
setting, defaultValue=default, type=QtGui.QColor) setting, defaultValue=default, type=QtGui.QColor
)
setattr(Chart.color, attr, color) setattr(Chart.color, attr, color)
self.callback_params[cp] = (setting, attr) self.callback_params[cp] = (setting, attr)
cp.clicked.connect(self.setColor) cp.clicked.connect(self.setColor)
@ -466,17 +494,18 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def changeShowMarkerNumber(self): def changeShowMarkerNumber(self):
Defaults.cfg.chart.marker_label = bool( Defaults.cfg.chart.marker_label = bool(
self.show_marker_number_option.isChecked()) self.show_marker_number_option.isChecked()
)
self.updateCharts() self.updateCharts()
def changeFilledMarkers(self): def changeFilledMarkers(self):
Defaults.cfg.chart.marker_filled = bool( Defaults.cfg.chart.marker_filled = bool(
self.filled_marker_option.isChecked()) self.filled_marker_option.isChecked()
)
self.updateCharts() self.updateCharts()
def changeMarkerAtTip(self): def changeMarkerAtTip(self):
Defaults.cfg.chart.marker_at_tip = bool( Defaults.cfg.chart.marker_at_tip = bool(self.marker_at_tip.isChecked())
self.marker_at_tip.isChecked())
self.updateCharts() self.updateCharts()
def changePointSize(self, size: int): def changePointSize(self, size: int):
@ -521,7 +550,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
color = getattr(Chart.color, attr) color = getattr(Chart.color, attr)
color = QtWidgets.QColorDialog.getColor( color = QtWidgets.QColorDialog.getColor(
color, options=QtWidgets.QColorDialog.ShowAlphaChannel) color, options=QtWidgets.QColorDialog.ShowAlphaChannel
)
if not color.isValid(): if not color.isValid():
logger.info("Invalid color") logger.info("Invalid color")
@ -566,7 +596,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
new_marker.updated.connect(self.app.markerUpdated) new_marker.updated.connect(self.app.markerUpdated)
label, layout = new_marker.getRow() label, layout = new_marker.getRow()
self.app.marker_control.layout.insertRow( self.app.marker_control.layout.insertRow(
Marker.count() - 1, label, layout) Marker.count() - 1, label, layout
)
self.btn_remove_marker.setDisabled(False) self.btn_remove_marker.setDisabled(False)
if Marker.count() >= 2: if Marker.count() >= 2:
@ -594,8 +625,12 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
def addVSWRMarker(self): def addVSWRMarker(self):
value, selected = QtWidgets.QInputDialog.getDouble( value, selected = QtWidgets.QInputDialog.getDouble(
self, "Add VSWR Marker", "VSWR value to show:", self,
min=1.001, decimals=3) "Add VSWR Marker",
"VSWR value to show:",
min=1.001,
decimals=3,
)
if selected: if selected:
self.vswrMarkers.append(value) self.vswrMarkers.append(value)
if self.vswr_marker_dropdown.itemText(0) == "None": if self.vswr_marker_dropdown.itemText(0) == "None":
@ -612,7 +647,8 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
value = float(value_str) value = float(value_str)
self.vswrMarkers.remove(value) self.vswrMarkers.remove(value)
self.vswr_marker_dropdown.removeItem( self.vswr_marker_dropdown.removeItem(
self.vswr_marker_dropdown.currentIndex()) self.vswr_marker_dropdown.currentIndex()
)
if self.vswr_marker_dropdown.count() == 0: if self.vswr_marker_dropdown.count() == 0:
self.vswr_marker_dropdown.addItem("None") self.vswr_marker_dropdown.addItem("None")
self.app.settings.remove("VSWRMarkers") self.app.settings.remove("VSWRMarkers")

Wyświetl plik

@ -68,27 +68,32 @@ class FilesWindow(QtWidgets.QWidget):
btn_open_file_window = QtWidgets.QPushButton("Files ...") btn_open_file_window = QtWidgets.QPushButton("Files ...")
btn_open_file_window.clicked.connect( 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): def exportFile(self, nr_params: int = 1):
if len(self.app.data.s11) == 0: if len(self.app.data.s11) == 0:
QtWidgets.QMessageBox.warning( 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 return
if nr_params > 2 and len(self.app.data.s21) == 0: if nr_params > 2 and len(self.app.data.s21) == 0:
QtWidgets.QMessageBox.warning( 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 return
filedialog = QtWidgets.QFileDialog(self) filedialog = QtWidgets.QFileDialog(self)
if nr_params == 1: if nr_params == 1:
filedialog.setDefaultSuffix("s1p") filedialog.setDefaultSuffix("s1p")
filedialog.setNameFilter( filedialog.setNameFilter(
"Touchstone 1-Port Files (*.s1p);;All files (*.*)") "Touchstone 1-Port Files (*.s1p);;All files (*.*)"
)
else: else:
filedialog.setDefaultSuffix("s2p") filedialog.setDefaultSuffix("s2p")
filedialog.setNameFilter( filedialog.setNameFilter(
"Touchstone 2-Port Files (*.s2p);;All files (*.*)") "Touchstone 2-Port Files (*.s2p);;All files (*.*)"
)
filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
selected = filedialog.exec() selected = filedialog.exec()
if not selected: if not selected:
@ -113,7 +118,8 @@ class FilesWindow(QtWidgets.QWidget):
def loadReferenceFile(self): def loadReferenceFile(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName( filename, _ = QtWidgets.QFileDialog.getOpenFileName(
filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)"
)
if filename != "": if filename != "":
self.app.resetReference() self.app.resetReference()
t = Touchstone(filename) t = Touchstone(filename)
@ -122,7 +128,8 @@ class FilesWindow(QtWidgets.QWidget):
def loadSweepFile(self): def loadSweepFile(self):
filename, _ = QtWidgets.QFileDialog.getOpenFileName( filename, _ = QtWidgets.QFileDialog.getOpenFileName(
filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)"
)
if filename != "": if filename != "":
self.app.data.s11 = [] self.app.data.s11 = []
self.app.data.s21 = [] self.app.data.s21 = []

Wyświetl plik

@ -28,12 +28,16 @@ logger = logging.getLogger(__name__)
class MarkerSettingsWindow(QtWidgets.QWidget): class MarkerSettingsWindow(QtWidgets.QWidget):
exampleData11 = [Datapoint(123000000, 0.89, -0.11), exampleData11 = [
Datapoint(123500000, 0.9, -0.1), Datapoint(123000000, 0.89, -0.11),
Datapoint(124000000, 0.91, -0.95)] Datapoint(123500000, 0.9, -0.1),
exampleData21 = [Datapoint(123000000, -0.25, 0.49), Datapoint(124000000, 0.91, -0.95),
Datapoint(123456000, -0.3, 0.5), ]
Datapoint(124000000, -0.2, 0.5)] 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): def __init__(self, app: QtWidgets.QWidget):
super().__init__() super().__init__()
@ -50,10 +54,10 @@ class MarkerSettingsWindow(QtWidgets.QWidget):
settings_group_box = QtWidgets.QGroupBox("Settings") settings_group_box = QtWidgets.QGroupBox("Settings")
settings_group_box_layout = QtWidgets.QFormLayout(settings_group_box) settings_group_box_layout = QtWidgets.QFormLayout(settings_group_box)
self.checkboxColouredMarker = QtWidgets.QCheckBox( self.checkboxColouredMarker = QtWidgets.QCheckBox("Colored marker name")
"Colored marker name")
self.checkboxColouredMarker.setChecked( self.checkboxColouredMarker.setChecked(
self.app.settings.value("ColoredMarkerNames", True, bool)) self.app.settings.value("ColoredMarkerNames", True, bool)
)
self.checkboxColouredMarker.stateChanged.connect(self.updateMarker) self.checkboxColouredMarker.stateChanged.connect(self.updateMarker)
settings_group_box_layout.addRow(self.checkboxColouredMarker) settings_group_box_layout.addRow(self.checkboxColouredMarker)
@ -103,7 +107,8 @@ class MarkerSettingsWindow(QtWidgets.QWidget):
def updateMarker(self): def updateMarker(self):
self.exampleMarker.setFrequency(123456000) self.exampleMarker.setFrequency(123456000)
self.exampleMarker.setColoredText( self.exampleMarker.setColoredText(
self.checkboxColouredMarker.isChecked()) self.checkboxColouredMarker.isChecked()
)
self.exampleMarker.setFieldSelection(self.currentFieldSelection) self.exampleMarker.setFieldSelection(self.currentFieldSelection)
self.exampleMarker.findLocation(self.exampleData11) self.exampleMarker.findLocation(self.exampleData11)
self.exampleMarker.resetLabels() self.exampleMarker.resetLabels()
@ -125,8 +130,11 @@ class MarkerSettingsWindow(QtWidgets.QWidget):
self.savedFieldSelection = self.currentFieldSelection[:] self.savedFieldSelection = self.currentFieldSelection[:]
self.app.settings.setValue("MarkerFields", self.savedFieldSelection) self.app.settings.setValue("MarkerFields", self.savedFieldSelection)
self.app.settings.setValue( self.app.settings.setValue(
"ColoredMarkerNames", self.checkboxColouredMarker.isChecked()) "ColoredMarkerNames", self.checkboxColouredMarker.isChecked()
for m in self.app.markers + [self.app.delta_marker, ]: )
for m in self.app.markers + [
self.app.delta_marker,
]:
m.setFieldSelection(self.savedFieldSelection) m.setFieldSelection(self.savedFieldSelection)
m.setColoredText(self.checkboxColouredMarker.isChecked()) m.setColoredText(self.checkboxColouredMarker.isChecked())

Wyświetl plik

@ -61,25 +61,34 @@ class ScreenshotWindow(QtWidgets.QLabel):
self.pix.scaled( self.pix.scaled(
self.size(), self.size(),
QtCore.Qt.KeepAspectRatio, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.FastTransformation)) QtCore.Qt.FastTransformation,
)
)
w, h = pixmap.width(), pixmap.height() w, h = pixmap.width(), pixmap.height()
self.action_original_size.setText( self.action_original_size.setText(
"Original size (" + str(w) + "x" + str(h) + ")") "Original size (" + str(w) + "x" + str(h) + ")"
)
self.action_2x_size.setText( 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( 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( 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( 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): def saveScreenshot(self):
if self.pix is not None: if self.pix is not None:
logger.info("Saving screenshot to file...") logger.info("Saving screenshot to file...")
filename, _ = QtWidgets.QFileDialog.getSaveFileName( filename, _ = QtWidgets.QFileDialog.getSaveFileName(
parent=self, caption="Save image", parent=self,
filter="PNG (*.png);;All files (*.*)") caption="Save image",
filter="PNG (*.png);;All files (*.*)",
)
logger.debug("Filename: %s", filename) logger.debug("Filename: %s", filename)
if filename != "": if filename != "":
@ -94,9 +103,13 @@ class ScreenshotWindow(QtWidgets.QLabel):
self.pix.scaled( self.pix.scaled(
self.size(), self.size(),
QtCore.Qt.KeepAspectRatio, QtCore.Qt.KeepAspectRatio,
QtCore.Qt.FastTransformation)) QtCore.Qt.FastTransformation,
)
)
def setScale(self, scale): def setScale(self, scale):
width, height = (self.pix.size().width() * scale, width, height = (
self.pix.size().height() * scale) self.pix.size().width() * scale,
self.pix.size().height() * scale,
)
self.resize(width, height) self.resize(width, height)

Wyświetl plik

@ -21,7 +21,8 @@ from functools import partial
from PyQt5 import QtWidgets, QtCore from PyQt5 import QtWidgets, QtCore
from NanoVNASaver.Formatting import ( from NanoVNASaver.Formatting import (
format_frequency_short, format_frequency_sweep, format_frequency_short,
format_frequency_sweep,
) )
from NanoVNASaver.Settings.Sweep import SweepMode from NanoVNASaver.Settings.Sweep import SweepMode
from NanoVNASaver.Windows.Defaults import make_scrollable 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 = QtWidgets.QLineEdit(self.app.sweep.properties.name)
input_title.setMinimumHeight(20) input_title.setMinimumHeight(20)
input_title.editingFinished.connect( input_title.editingFinished.connect(
lambda: self.update_title(input_title.text())) lambda: self.update_title(input_title.text())
)
layout.addRow(input_title) layout.addRow(input_title)
return box return box
def settings_box(self) -> 'QtWidgets.QWidget': def settings_box(self) -> "QtWidgets.QWidget":
box = QtWidgets.QGroupBox("Settings") box = QtWidgets.QGroupBox("Settings")
layout = QtWidgets.QFormLayout(box) layout = QtWidgets.QFormLayout(box)
@ -73,25 +75,29 @@ class SweepSettingsWindow(QtWidgets.QWidget):
radio_button = QtWidgets.QRadioButton("Single sweep") radio_button = QtWidgets.QRadioButton("Single sweep")
radio_button.setMinimumHeight(20) radio_button.setMinimumHeight(20)
radio_button.setChecked( radio_button.setChecked(
self.app.sweep.properties.mode == SweepMode.SINGLE) self.app.sweep.properties.mode == SweepMode.SINGLE
radio_button.clicked.connect( )
lambda: self.update_mode(SweepMode.SINGLE)) radio_button.clicked.connect(lambda: self.update_mode(SweepMode.SINGLE))
sweep_btn_layout.addWidget(radio_button) sweep_btn_layout.addWidget(radio_button)
radio_button = QtWidgets.QRadioButton("Continous sweep") radio_button = QtWidgets.QRadioButton("Continous sweep")
radio_button.setMinimumHeight(20) radio_button.setMinimumHeight(20)
radio_button.setChecked( radio_button.setChecked(
self.app.sweep.properties.mode == SweepMode.CONTINOUS) self.app.sweep.properties.mode == SweepMode.CONTINOUS
)
radio_button.clicked.connect( radio_button.clicked.connect(
lambda: self.update_mode(SweepMode.CONTINOUS)) lambda: self.update_mode(SweepMode.CONTINOUS)
)
sweep_btn_layout.addWidget(radio_button) sweep_btn_layout.addWidget(radio_button)
radio_button = QtWidgets.QRadioButton("Averaged sweep") radio_button = QtWidgets.QRadioButton("Averaged sweep")
radio_button.setMinimumHeight(20) radio_button.setMinimumHeight(20)
radio_button.setChecked( radio_button.setChecked(
self.app.sweep.properties.mode == SweepMode.AVERAGE) self.app.sweep.properties.mode == SweepMode.AVERAGE
)
radio_button.clicked.connect( radio_button.clicked.connect(
lambda: self.update_mode(SweepMode.AVERAGE)) lambda: self.update_mode(SweepMode.AVERAGE)
)
sweep_btn_layout.addWidget(radio_button) sweep_btn_layout.addWidget(radio_button)
layout.addRow(sweep_btn_layout) layout.addRow(sweep_btn_layout)
@ -101,7 +107,8 @@ class SweepSettingsWindow(QtWidgets.QWidget):
"Logarithmic sweeping changes the step width in each segment" "Logarithmic sweeping changes the step width in each segment"
" in logarithmical manner. Useful in conjunction with small" " in logarithmical manner. Useful in conjunction with small"
" amount of datapoints and many segments. Step display in" " amount of datapoints and many segments. Step display in"
" SweepControl cannot reflect this currently.") " SweepControl cannot reflect this currently."
)
label.setWordWrap(True) label.setWordWrap(True)
label.setMinimumSize(600, 70) label.setMinimumSize(600, 70)
layout.addRow(label) layout.addRow(label)
@ -109,26 +116,32 @@ class SweepSettingsWindow(QtWidgets.QWidget):
checkbox.setMinimumHeight(20) checkbox.setMinimumHeight(20)
checkbox.setCheckState(self.app.sweep.properties.logarithmic) checkbox.setCheckState(self.app.sweep.properties.logarithmic)
checkbox.toggled.connect( checkbox.toggled.connect(
lambda: self.update_logarithmic(checkbox.isChecked())) lambda: self.update_logarithmic(checkbox.isChecked())
)
layout.addRow(checkbox) layout.addRow(checkbox)
# Averaging # Averaging
label = QtWidgets.QLabel( label = QtWidgets.QLabel(
"Averaging allows discarding outlying samples to get better" "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.setWordWrap(True)
label.setMinimumHeight(50) label.setMinimumHeight(50)
layout.addRow(label) layout.addRow(label)
averages = QtWidgets.QLineEdit( averages = QtWidgets.QLineEdit(
str(self.app.sweep.properties.averages[0])) str(self.app.sweep.properties.averages[0])
)
averages.setMinimumHeight(20) averages.setMinimumHeight(20)
truncates = QtWidgets.QLineEdit( truncates = QtWidgets.QLineEdit(
str(self.app.sweep.properties.averages[1])) str(self.app.sweep.properties.averages[1])
)
truncates.setMinimumHeight(20) truncates.setMinimumHeight(20)
averages.editingFinished.connect( averages.editingFinished.connect(
lambda: self.update_averaging(averages, truncates)) lambda: self.update_averaging(averages, truncates)
)
truncates.editingFinished.connect( 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 of measurements to average", averages)
layout.addRow("Number to discard", truncates) layout.addRow("Number to discard", truncates)
@ -136,7 +149,8 @@ class SweepSettingsWindow(QtWidgets.QWidget):
label = QtWidgets.QLabel( label = QtWidgets.QLabel(
"Some times when you measure amplifiers you need to use an" "Some times when you measure amplifiers you need to use an"
" attenuator in line with the S21 input (CH1) here you can" " attenuator in line with the S21 input (CH1) here you can"
" specify it.") " specify it."
)
label.setWordWrap(True) label.setWordWrap(True)
label.setMinimumHeight(50) label.setMinimumHeight(50)
layout.addRow(label) layout.addRow(label)
@ -144,11 +158,12 @@ class SweepSettingsWindow(QtWidgets.QWidget):
input_att = QtWidgets.QLineEdit(str(self.app.s21att)) input_att = QtWidgets.QLineEdit(str(self.app.s21att))
input_att.setMinimumHeight(20) input_att.setMinimumHeight(20)
input_att.editingFinished.connect( 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) layout.addRow("Attenuator in port CH1 (s21) in dB", input_att)
return box return box
def sweep_box(self) -> 'QtWidgets.QWidget': def sweep_box(self) -> "QtWidgets.QWidget":
box = QtWidgets.QGroupBox("Sweep band") box = QtWidgets.QGroupBox("Sweep band")
layout = QtWidgets.QFormLayout(box) layout = QtWidgets.QFormLayout(box)
sweep_pad_layout = QtWidgets.QHBoxLayout() sweep_pad_layout = QtWidgets.QHBoxLayout()
@ -162,7 +177,11 @@ class SweepSettingsWindow(QtWidgets.QWidget):
sweep_pad_layout.addWidget(QtWidgets.QLabel("Pad band limits:")) sweep_pad_layout.addWidget(QtWidgets.QLabel("Pad band limits:"))
for btn_label, value in ( 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 = QtWidgets.QRadioButton(btn_label)
radio_button.setMinimumHeight(20) radio_button.setMinimumHeight(20)
radio_button.setChecked(self.padding == value) radio_button.setChecked(self.padding == value)
@ -186,20 +205,33 @@ class SweepSettingsWindow(QtWidgets.QWidget):
power_sel = QtWidgets.QComboBox() power_sel = QtWidgets.QComboBox()
power_sel.addItems(power_descs) power_sel.addItems(power_descs)
power_sel.currentTextChanged.connect( power_sel.currentTextChanged.connect(
partial(self.update_tx_power, freq_range)) partial(self.update_tx_power, freq_range)
self._power_layout.addRow("TX power {}..{}".format( )
*map(format_frequency_short, freq_range)), power_sel) self._power_layout.addRow(
"TX power {}..{}".format(
*map(format_frequency_short, freq_range)
),
power_sel,
)
def update_band(self, apply: bool = False): def update_band(self, apply: bool = False):
logger.debug("update_band(%s)", apply) logger.debug("update_band(%s)", apply)
index_start = self.band_list.model().index( index_start = self.band_list.model().index(
self.band_list.currentIndex(), 1) self.band_list.currentIndex(), 1
)
index_stop = self.band_list.model().index( index_stop = self.band_list.model().index(
self.band_list.currentIndex(), 2) self.band_list.currentIndex(), 2
start = int(self.band_list.model().data( )
index_start, QtCore.Qt.ItemDataRole).value()) start = int(
stop = int(self.band_list.model().data( self.band_list.model()
index_stop, QtCore.Qt.ItemDataRole).value()) .data(index_start, QtCore.Qt.ItemDataRole)
.value()
)
stop = int(
self.band_list.model()
.data(index_stop, QtCore.Qt.ItemDataRole)
.value()
)
if self.padding > 0: if self.padding > 0:
span = stop - start span = stop - start
@ -209,33 +241,37 @@ class SweepSettingsWindow(QtWidgets.QWidget):
self.band_label.setText( self.band_label.setText(
f"Sweep span: {format_frequency_short(start)}" f"Sweep span: {format_frequency_short(start)}"
f" to {format_frequency_short(stop)}") f" to {format_frequency_short(stop)}"
)
if not apply: if not apply:
return return
self.app.sweep_control.input_start.setText( self.app.sweep_control.input_start.setText(
format_frequency_sweep(start)) format_frequency_sweep(start)
self.app.sweep_control.input_end.setText( )
format_frequency_sweep(stop)) 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.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: try:
att = float(value.text()) att = float(value.text())
assert att >= 0 assert att >= 0
except (ValueError, AssertionError): except (ValueError, AssertionError):
logger.warning("Values for attenuator are absolute and with no" logger.warning(
" minus sign, resetting.") "Values for attenuator are absolute and with no"
" minus sign, resetting."
)
att = 0 att = 0
logger.debug("Attenuator %sdB inline with S21 input", att) logger.debug("Attenuator %sdB inline with S21 input", att)
value.setText(str(att)) value.setText(str(att))
self.app.s21att = att self.app.s21att = att
def update_averaging(self, def update_averaging(
averages: 'QtWidgets.QLineEdit', self, averages: "QtWidgets.QLineEdit", truncs: "QtWidgets.QLineEdit"
truncs: 'QtWidgets.QLineEdit'): ):
try: try:
amount = int(averages.text()) amount = int(averages.text())
truncates = int(truncs.text()) truncates = int(truncs.text())
@ -257,7 +293,7 @@ class SweepSettingsWindow(QtWidgets.QWidget):
with self.app.sweep.lock: with self.app.sweep.lock:
self.app.sweep.properties.logarithmic = logarithmic self.app.sweep.properties.logarithmic = logarithmic
def update_mode(self, mode: 'SweepMode'): def update_mode(self, mode: "SweepMode"):
logger.debug("update_mode(%s)", mode) logger.debug("update_mode(%s)", mode)
with self.app.sweep.lock: with self.app.sweep.lock:
self.app.sweep.properties.mode = mode self.app.sweep.properties.mode = mode

Wyświetl plik

@ -20,6 +20,7 @@ import logging
import math import math
import numpy as np import numpy as np
# pylint: disable=import-error, no-name-in-module # pylint: disable=import-error, no-name-in-module
from scipy.signal import convolve from scipy.signal import convolve
from scipy.constants import speed_of_light 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-8/U (Shireen RFC®400 Low Loss) (0.86)", 0.86),
("RG-8X (Belden 9258) (0.82)", 0.82), ("RG-8X (Belden 9258) (0.82)", 0.82),
# Next three added by EKZ, KC3KZ, from measurement of actual cable # Next three added by EKZ, KC3KZ, from measurement of actual cable
("RG-8X (Wireman \"Super 8\" CQ106) (0.81)", 0.81), ('RG-8X (Wireman "Super 8" CQ106) (0.81)', 0.81),
("RG-8X (Wireman \"MINI-8 Lo-Loss\" CQ118) (0.82)", 0.82), ('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-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-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-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), ("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: for cable_name, velocity in CABLE_PARAMETERS:
self.tdr_velocity_dropdown.addItem(cable_name, velocity) self.tdr_velocity_dropdown.addItem(cable_name, velocity)
self.tdr_velocity_dropdown.insertSeparator( 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.addItem("Custom", -1)
self.tdr_velocity_dropdown.setCurrentIndex(1) # Default to PE (0.66) self.tdr_velocity_dropdown.setCurrentIndex(1) # Default to PE (0.66)
self.tdr_velocity_dropdown.currentIndexChanged.connect(self.updateTDR) self.tdr_velocity_dropdown.currentIndexChanged.connect(self.updateTDR)
@ -121,7 +123,8 @@ class TDRWindow(QtWidgets.QWidget):
else: else:
self.tdr_velocity_input.setDisabled(True) self.tdr_velocity_input.setDisabled(True)
self.tdr_velocity_input.setText( self.tdr_velocity_input.setText(
str(self.tdr_velocity_dropdown.currentData())) str(self.tdr_velocity_dropdown.currentData())
)
try: try:
v = float(self.tdr_velocity_input.text()) v = float(self.tdr_velocity_input.text())
@ -142,8 +145,7 @@ class TDRWindow(QtWidgets.QWidget):
step = np.ones(FFT_POINTS) step = np.ones(FFT_POINTS)
step_response = convolve(td, step) step_response = convolve(td, step)
self.step_response_Z = 50 * ( self.step_response_Z = 50 * (1 + step_response) / (1 - step_response)
1 + step_response) / (1 - step_response)
time_axis = np.linspace(0, 1 / step_size, FFT_POINTS) time_axis = np.linspace(0, 1 / step_size, FFT_POINTS)
self.distance_axis = time_axis * v * speed_of_light self.distance_axis = time_axis * v * speed_of_light

Wyświetl plik

@ -9,16 +9,17 @@ from .MarkerSettings import MarkerSettingsWindow
from .Screenshot import ScreenshotWindow from .Screenshot import ScreenshotWindow
from .SweepSettings import SweepSettingsWindow from .SweepSettings import SweepSettingsWindow
from .TDR import TDRWindow from .TDR import TDRWindow
__all__ = [ __all__ = [
'AboutWindow', "AboutWindow",
'AnalysisWindow', "AnalysisWindow",
'BandsWindow', "BandsWindow",
'CalibrationWindow', "CalibrationWindow",
'DeviceSettingsWindow', "DeviceSettingsWindow",
'DisplaySettingsWindow', "DisplaySettingsWindow",
'FilesWindow', "FilesWindow",
'MarkerSettingsWindow', "MarkerSettingsWindow",
'ScreenshotWindow', "ScreenshotWindow",
'SweepSettingsWindow', "SweepSettingsWindow",
'TDRWindow', "TDRWindow",
] ]

Wyświetl plik

@ -2,9 +2,15 @@ import sys
if sys.version_info[:2] >= (3, 8): if sys.version_info[:2] >= (3, 8):
# TODO: Import directly (no need for conditional) when `python_requires = >= 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: else:
from importlib_metadata import PackageNotFoundError, version # pragma: no cover from importlib_metadata import (
PackageNotFoundError,
version,
) # pragma: no cover
try: try:
# Change here if project is renamed and does not equal the package name # Change here if project is renamed and does not equal the package name

Wyświetl plik

@ -40,19 +40,27 @@ from NanoVNASaver.Touchstone import Touchstone
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description=__doc__, description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter) formatter_class=argparse.RawDescriptionHelpFormatter,
parser.add_argument("-d", "--debug", action="store_true", )
help="Set loglevel to debug") parser.add_argument(
parser.add_argument("-D", "--debug-file", "-d", "--debug", action="store_true", help="Set loglevel to debug"
help="File to write debug logging output to") )
parser.add_argument("-f", "--file", parser.add_argument(
help="Touchstone file to load as sweep for off" "-D", "--debug-file", help="File to write debug logging output to"
" device usage") )
parser.add_argument("-r", "--ref-file", parser.add_argument(
help="Touchstone file to load as reference for off" "-f",
" device usage") "--file",
parser.add_argument("--version", action="version", help="Touchstone file to load as sweep for off" " device usage",
version=f"NanoVNASaver {VERSION}") )
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() args = parser.parse_args()
console_log_level = logging.WARNING console_log_level = logging.WARNING
@ -69,7 +77,8 @@ def main():
ch = logging.StreamHandler() ch = logging.StreamHandler()
ch.setLevel(console_log_level) ch.setLevel(console_log_level)
formatter = logging.Formatter( formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s') "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
ch.setFormatter(formatter) ch.setFormatter(formatter)
logger.addHandler(ch) logger.addHandler(ch)
@ -81,8 +90,7 @@ def main():
logger.info("Startup...") logger.info("Startup...")
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
True)
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
window = NanoVNASaver() window = NanoVNASaver()
window.show() window.show()
@ -104,5 +112,5 @@ def main():
raise exc raise exc
if __name__ == '__main__': if __name__ == "__main__":
main() main()