kopia lustrzana https://github.com/NanoVNA-Saver/nanovna-saver
used black for reformatting/lintig
rodzic
b0110002ec
commit
50b540a832
|
@ -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()
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 ?!?
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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}")
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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))
|
||||||
)
|
)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}",
|
||||||
|
)
|
||||||
|
|
|
@ -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
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 ""
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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)}"
|
||||||
|
)
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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)
|
||||||
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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}")
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Ładowanie…
Reference in New Issue