kopia lustrzana https://github.com/NanoVNA-Saver/nanovna-saver
Merge branch 'Development' into serial-to-parallel-test-fix
commit
2a36fc80be
|
@ -0,0 +1,20 @@
|
|||
[run]
|
||||
# ignore GUI code atm.
|
||||
omit =
|
||||
NanoVNASaver/Analysis.py
|
||||
NanoVNASaver/Calibration.py
|
||||
NanoVNASaver/Chart.py
|
||||
NanoVNASaver/Hardware.py
|
||||
NanoVNASaver/Inputs.py
|
||||
NanoVNASaver/Marker/Settings.py
|
||||
NanoVNASaver/Marker/Values.py
|
||||
NanoVNASaver/Marker/Widget.py
|
||||
NanoVNASaver/Marker/__init__.py
|
||||
NanoVNASaver/NanoVNASaver.py
|
||||
NanoVNASaver/SweepWorker.py
|
||||
NanoVNASaver/__init__.py
|
||||
NanoVNASaver/__main__.py
|
||||
NanoVNASaver/about.py
|
||||
[report]
|
||||
fail_under = 90.0
|
||||
show_missing = True
|
|
@ -21,8 +21,7 @@ jobs:
|
|||
run: |
|
||||
pip install pylint
|
||||
pylint --exit-zero NanoVNASaver
|
||||
- name: Unittests
|
||||
- name: Unittests / Coverage
|
||||
run: |
|
||||
pip install coverage
|
||||
coverage run ./test_master.py
|
||||
coverage report --fail-under=90 -m --skip-covered
|
||||
pip install pytest-cov
|
||||
pytest --cov=NanoVNASaver
|
||||
|
|
|
@ -9,3 +9,4 @@
|
|||
*.cal
|
||||
settings.json
|
||||
.gitignore
|
||||
.coverage
|
||||
|
|
|
@ -764,11 +764,15 @@ class FrequencyChart(Chart):
|
|||
for i in range(len(data)):
|
||||
x = self.getXPosition(data[i])
|
||||
y = y_function(data[i])
|
||||
if y is None:
|
||||
continue
|
||||
if self.isPlotable(x, y):
|
||||
qp.drawPoint(int(x), int(y))
|
||||
if self.drawLines and i > 0:
|
||||
prevx = self.getXPosition(data[i - 1])
|
||||
prevy = y_function(data[i - 1])
|
||||
if prevy is None:
|
||||
continue
|
||||
qp.setPen(line_pen)
|
||||
if self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
|
@ -795,7 +799,8 @@ class FrequencyChart(Chart):
|
|||
self.drawMarker(x, y, qp, m.color, self.markers.index(m)+1)
|
||||
|
||||
def isPlotable(self, x, y):
|
||||
return self.leftMargin <= x <= self.leftMargin + self.chartWidth and \
|
||||
return y is not None and x is not None and \
|
||||
self.leftMargin <= x <= self.leftMargin + self.chartWidth and \
|
||||
self.topMargin <= y <= self.topMargin + self.chartHeight
|
||||
|
||||
def getPlotable(self, x, y, distantx, distanty):
|
||||
|
@ -1569,6 +1574,8 @@ class LogMagChart(FrequencyChart):
|
|||
maxValue = -100
|
||||
for d in self.data:
|
||||
logmag = self.logMag(d)
|
||||
if math.isinf(logmag):
|
||||
continue
|
||||
if logmag > maxValue:
|
||||
maxValue = logmag
|
||||
if logmag < minValue:
|
||||
|
@ -1577,6 +1584,8 @@ class LogMagChart(FrequencyChart):
|
|||
if d.freq < self.fstart or d.freq > self.fstop:
|
||||
continue
|
||||
logmag = self.logMag(d)
|
||||
if math.isinf(logmag):
|
||||
continue
|
||||
if logmag > maxValue:
|
||||
maxValue = logmag
|
||||
if logmag < minValue:
|
||||
|
@ -1673,6 +1682,8 @@ class LogMagChart(FrequencyChart):
|
|||
|
||||
def getYPosition(self, d: Datapoint) -> int:
|
||||
logMag = self.logMag(d)
|
||||
if math.isinf(logMag):
|
||||
return None
|
||||
return self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight)
|
||||
|
||||
def valueAtPosition(self, y) -> List[float]:
|
||||
|
@ -1946,29 +1957,37 @@ class CombinedLogMagChart(FrequencyChart):
|
|||
maxValue = 0
|
||||
for d in self.data11:
|
||||
logmag = self.logMag(d)
|
||||
if math.isinf(logmag):
|
||||
continue
|
||||
if logmag > maxValue:
|
||||
maxValue = logmag
|
||||
if logmag < minValue:
|
||||
minValue = logmag
|
||||
for d in self.data21:
|
||||
logmag = self.logMag(d)
|
||||
if math.isinf(logmag):
|
||||
continue
|
||||
if logmag > maxValue:
|
||||
maxValue = logmag
|
||||
if logmag < minValue:
|
||||
minValue = logmag
|
||||
|
||||
for d in self.reference11: # Also check min/max for the reference sweep
|
||||
for d in self.reference11:
|
||||
if d.freq < self.fstart or d.freq > self.fstop:
|
||||
continue
|
||||
logmag = self.logMag(d)
|
||||
if math.isinf(logmag):
|
||||
continue
|
||||
if logmag > maxValue:
|
||||
maxValue = logmag
|
||||
if logmag < minValue:
|
||||
minValue = logmag
|
||||
for d in self.reference21: # Also check min/max for the reference sweep
|
||||
for d in self.reference21:
|
||||
if d.freq < self.fstart or d.freq > self.fstop:
|
||||
continue
|
||||
logmag = self.logMag(d)
|
||||
if math.isinf(logmag):
|
||||
continue
|
||||
if logmag > maxValue:
|
||||
maxValue = logmag
|
||||
if logmag < minValue:
|
||||
|
@ -2096,6 +2115,8 @@ class CombinedLogMagChart(FrequencyChart):
|
|||
|
||||
def getYPosition(self, d: Datapoint) -> int:
|
||||
logMag = self.logMag(d)
|
||||
if math.isinf(logMag):
|
||||
return None
|
||||
return self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight)
|
||||
|
||||
def valueAtPosition(self, y) -> List[float]:
|
||||
|
@ -3818,7 +3839,7 @@ class GroupDelayChart(FrequencyChart):
|
|||
for d in self.reference:
|
||||
rawReference.append(d.phase)
|
||||
|
||||
if len(self.data) > 0:
|
||||
if len(self.data) > 1:
|
||||
unwrappedData = np.degrees(np.unwrap(rawData))
|
||||
self.groupDelay = []
|
||||
for i in range(len(self.data)):
|
||||
|
@ -3838,7 +3859,7 @@ class GroupDelayChart(FrequencyChart):
|
|||
delay /= 2
|
||||
self.groupDelay.append(delay)
|
||||
|
||||
if len(self.reference) > 0:
|
||||
if len(self.reference) > 1:
|
||||
unwrappedReference = np.degrees(np.unwrap(rawReference))
|
||||
self.groupDelayReference = []
|
||||
for i in range(len(self.reference)):
|
||||
|
@ -4096,7 +4117,7 @@ class CapacitanceChart(FrequencyChart):
|
|||
self.span = span
|
||||
|
||||
target_ticks = math.floor(self.chartHeight / 60)
|
||||
fmt = Format(max_nr_digits=3)
|
||||
fmt = Format(max_nr_digits=1)
|
||||
for i in range(target_ticks):
|
||||
val = minValue + (i / target_ticks) * span
|
||||
y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight)
|
||||
|
@ -4223,7 +4244,7 @@ class InductanceChart(FrequencyChart):
|
|||
self.span = span
|
||||
|
||||
target_ticks = math.floor(self.chartHeight / 60)
|
||||
fmt = Format(max_nr_digits=3)
|
||||
fmt = Format(max_nr_digits=1)
|
||||
for i in range(target_ticks):
|
||||
val = minValue + (i / target_ticks) * span
|
||||
y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight)
|
||||
|
|
|
@ -23,6 +23,8 @@ FMT_FREQ_INPUTS = SITools.Format(max_nr_digits=10, allow_strip=True, printable_m
|
|||
FMT_Q_FACTOR = SITools.Format(max_nr_digits=4, assume_infinity=False, min_offset=0, max_offset=0, allow_strip=True)
|
||||
FMT_GROUP_DELAY = SITools.Format(max_nr_digits=5, space_str=" ")
|
||||
FMT_REACT = SITools.Format(max_nr_digits=5, space_str=" ", allow_strip=True)
|
||||
FMT_COMPLEX = SITools.Format(max_nr_digits=3, allow_strip=True,
|
||||
printable_min=0, unprintable_under="- ")
|
||||
|
||||
|
||||
def format_frequency(freq: float, fmt=FMT_FREQ) -> str:
|
||||
|
@ -48,6 +50,8 @@ def format_q_factor(val: float) -> str:
|
|||
def format_vswr(val: float) -> str:
|
||||
return f"{val:.3f}"
|
||||
|
||||
def format_magnitude(val: float) -> str:
|
||||
return f"{val:.3f}"
|
||||
|
||||
def format_resistance(val: float) -> str:
|
||||
if val < 0:
|
||||
|
@ -68,7 +72,7 @@ def format_inductance(val: float, allow_negative: bool = True) -> str:
|
|||
|
||||
|
||||
def format_group_delay(val: float) -> str:
|
||||
return str(SITools.Value(val, "s", fmt=FMT_GROUP_DELAY))
|
||||
return str(SITools.Value(val, "s", FMT_GROUP_DELAY))
|
||||
|
||||
|
||||
def format_phase(val: float) -> str:
|
||||
|
@ -76,21 +80,6 @@ def format_phase(val: float) -> str:
|
|||
|
||||
|
||||
def format_complex_imp(z: complex) -> str:
|
||||
if z.real > 0:
|
||||
if z.real >= 1000:
|
||||
s = f"{z.real/1000:.3g}k"
|
||||
else:
|
||||
s = f"{z.real:.4g}"
|
||||
else:
|
||||
s = "- "
|
||||
if z.imag < 0:
|
||||
s += " -j"
|
||||
else:
|
||||
s += " +j"
|
||||
if abs(z.imag) >= 1000:
|
||||
s += f"{abs(z.imag)/1000:.3g}k"
|
||||
elif abs(z.imag) < 0.1:
|
||||
s += f"{abs(z.imag)*1000:.3g}m"
|
||||
else:
|
||||
s += f"{abs(z.imag):.3g}"
|
||||
return s + " \N{OHM SIGN}"
|
||||
re = SITools.Value(z.real, 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}"
|
||||
|
|
|
@ -31,6 +31,7 @@ class VNA:
|
|||
name = "VNA"
|
||||
validateInput = True
|
||||
features = []
|
||||
datapoints = 101
|
||||
|
||||
def __init__(self, app, serial_port: serial.Serial):
|
||||
from NanoVNASaver.NanoVNASaver import NanoVNASaver
|
||||
|
@ -44,6 +45,9 @@ class VNA:
|
|||
tmp_vna = VNA(app, serial_port)
|
||||
tmp_vna.flushSerialBuffers()
|
||||
firmware = tmp_vna.readFirmware()
|
||||
if firmware.find("AVNA + Teensy") > 0:
|
||||
logger.info("Type: AVNA")
|
||||
return AVNA(app, serial_port)
|
||||
if firmware.find("NanoVNA-H") > 0:
|
||||
logger.info("Type: NanoVNA-H")
|
||||
return NanoVNA_H(app, serial_port)
|
||||
|
@ -190,11 +194,12 @@ class VNA:
|
|||
return
|
||||
|
||||
def setSweep(self, start, stop):
|
||||
self.writeSerial("sweep " + str(start) + " " + str(stop) + " 101")
|
||||
self.writeSerial("sweep " + str(start) + " " + str(stop) + " " + str(self.datapoints))
|
||||
|
||||
|
||||
class InvalidVNA(VNA):
|
||||
name = "Invalid"
|
||||
datapoints = 0
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
@ -227,8 +232,92 @@ class InvalidVNA(VNA):
|
|||
return
|
||||
|
||||
|
||||
class AVNA(VNA):
|
||||
name = "AVNA"
|
||||
datapoints = 101
|
||||
|
||||
def __init__(self, app, serial_port):
|
||||
super().__init__(app, serial_port)
|
||||
self.version = Version(self.readVersion())
|
||||
|
||||
self.features = []
|
||||
self.features.append("Customizable data points")
|
||||
|
||||
def isValid(self):
|
||||
return True
|
||||
|
||||
def getCalibration(self) -> str:
|
||||
logger.debug("Reading calibration info.")
|
||||
if not self.serial.is_open:
|
||||
return "Not connected."
|
||||
if self.app.serialLock.acquire():
|
||||
try:
|
||||
data = "a"
|
||||
while data != "":
|
||||
data = self.serial.readline().decode('ascii')
|
||||
self.serial.write("cal\r".encode('ascii'))
|
||||
result = ""
|
||||
data = ""
|
||||
sleep(0.1)
|
||||
while "ch>" not in data:
|
||||
data = self.serial.readline().decode('ascii')
|
||||
result += data
|
||||
values = result.splitlines()
|
||||
return values[1]
|
||||
except serial.SerialException as exc:
|
||||
logger.exception("Exception while reading calibration info: %s", exc)
|
||||
finally:
|
||||
self.app.serialLock.release()
|
||||
return "Unknown"
|
||||
|
||||
def readFrequencies(self) -> List[str]:
|
||||
return self.readValues("frequencies")
|
||||
|
||||
def readValues11(self) -> List[str]:
|
||||
return self.readValues("data 0")
|
||||
|
||||
def readValues21(self) -> List[str]:
|
||||
return self.readValues("data 1")
|
||||
|
||||
def resetSweep(self, start: int, stop: int):
|
||||
self.writeSerial("sweep " + str(start) + " " + str(stop) + " " + str(self.datapoints))
|
||||
self.writeSerial("resume")
|
||||
|
||||
def readVersion(self):
|
||||
logger.debug("Reading version info.")
|
||||
if not self.serial.is_open:
|
||||
return
|
||||
if self.app.serialLock.acquire():
|
||||
try:
|
||||
data = "a"
|
||||
while data != "":
|
||||
data = self.serial.readline().decode('ascii')
|
||||
self.serial.write("version\r".encode('ascii'))
|
||||
result = ""
|
||||
data = ""
|
||||
sleep(0.1)
|
||||
while "ch>" not in data:
|
||||
data = self.serial.readline().decode('ascii')
|
||||
result += data
|
||||
values = result.splitlines()
|
||||
logger.debug("Found version info: %s", values[1])
|
||||
return values[1]
|
||||
except serial.SerialException as exc:
|
||||
logger.exception("Exception while reading firmware version: %s", exc)
|
||||
finally:
|
||||
self.app.serialLock.release()
|
||||
return
|
||||
|
||||
def setSweep(self, start, stop):
|
||||
self.writeSerial("sweep " + str(start) + " " + str(stop) + " " + str(self.datapoints))
|
||||
sleep(1)
|
||||
|
||||
|
||||
class NanoVNA(VNA):
|
||||
name = "NanoVNA"
|
||||
datapoints = 101
|
||||
screenwidth = 320
|
||||
screenheight = 240
|
||||
|
||||
def __init__(self, app, serial_port):
|
||||
super().__init__(app, serial_port)
|
||||
|
@ -289,17 +378,17 @@ class NanoVNA(VNA):
|
|||
data = self.serial.readline().decode('ascii')
|
||||
self.serial.write("capture\r".encode('ascii'))
|
||||
timeout = self.serial.timeout
|
||||
self.serial.timeout = 2
|
||||
self.serial.timeout = 4
|
||||
self.serial.readline()
|
||||
image_data = self.serial.read(320 * 240 * 2)
|
||||
image_data = self.serial.read(self.screenwidth * self.screenheight * 2)
|
||||
self.serial.timeout = timeout
|
||||
rgb_data = struct.unpack(">76800H", image_data)
|
||||
rgb_data = struct.unpack(">" + str(self.screenwidth * self.screenheight) + "H", image_data)
|
||||
rgb_array = np.array(rgb_data, dtype=np.uint32)
|
||||
rgba_array = (0xFF000000 +
|
||||
((rgb_array & 0xF800) << 8) +
|
||||
((rgb_array & 0x07E0) << 5) +
|
||||
((rgb_array & 0x001F) << 3))
|
||||
image = QtGui.QImage(rgba_array, 320, 240, QtGui.QImage.Format_ARGB32)
|
||||
image = QtGui.QImage(rgba_array, self.screenwidth, self.screenheight, QtGui.QImage.Format_ARGB32)
|
||||
logger.debug("Captured screenshot")
|
||||
return QtGui.QPixmap(image)
|
||||
except serial.SerialException as exc:
|
||||
|
@ -318,7 +407,7 @@ class NanoVNA(VNA):
|
|||
return self.readValues("data 1")
|
||||
|
||||
def resetSweep(self, start: int, stop: int):
|
||||
self.writeSerial("sweep " + str(start) + " " + str(stop) + " 101")
|
||||
self.writeSerial("sweep " + str(start) + " " + str(stop) + " " + str(self.datapoints))
|
||||
self.writeSerial("resume")
|
||||
|
||||
def readVersion(self):
|
||||
|
@ -348,9 +437,9 @@ class NanoVNA(VNA):
|
|||
|
||||
def setSweep(self, start, stop):
|
||||
if self.useScan:
|
||||
self.writeSerial("scan " + str(start) + " " + str(stop) + " 101")
|
||||
self.writeSerial("scan " + str(start) + " " + str(stop) + " " + str(self.datapoints))
|
||||
else:
|
||||
self.writeSerial("sweep " + str(start) + " " + str(stop) + " 101")
|
||||
self.writeSerial("sweep " + str(start) + " " + str(stop) + " " + str(self.datapoints))
|
||||
sleep(1)
|
||||
|
||||
|
||||
|
@ -358,8 +447,75 @@ class NanoVNA_H(NanoVNA):
|
|||
name = "NanoVNA-H"
|
||||
|
||||
|
||||
class NanoVNA_H4(NanoVNA):
|
||||
name = "NanoVNA-H4"
|
||||
screenwidth = 640
|
||||
screenheight = 240
|
||||
|
||||
|
||||
class NanoVNA_F(NanoVNA):
|
||||
name = "NanoVNA-F"
|
||||
screenwidth = 800
|
||||
screenheight = 480
|
||||
|
||||
def getScreenshot(self) -> QtGui.QPixmap:
|
||||
logger.debug("Capturing screenshot...")
|
||||
if not self.serial.is_open:
|
||||
return QtGui.QPixmap()
|
||||
if self.app.serialLock.acquire():
|
||||
try:
|
||||
data = "a"
|
||||
while data != "":
|
||||
data = self.serial.readline().decode('ascii')
|
||||
self.serial.write("capture\r".encode('ascii'))
|
||||
timeout = self.serial.timeout
|
||||
self.serial.timeout = 4
|
||||
self.serial.readline()
|
||||
image_data = self.serial.read(self.screenwidth * self.screenheight * 2)
|
||||
self.serial.timeout = timeout
|
||||
rgb_data = struct.unpack("<" + str(self.screenwidth * self.screenheight) + "H", image_data)
|
||||
rgb_array = np.array(rgb_data, dtype=np.uint32)
|
||||
rgba_array = (0xFF000000 +
|
||||
((rgb_array & 0xF800) << 8) + # G?!
|
||||
((rgb_array & 0x07E0) >> 3) + # B
|
||||
((rgb_array & 0x001F) << 11)) # G
|
||||
|
||||
# logger.debug("Value yellow: %s", hex(rgb_array[10*400+36])) # This ought to be yellow
|
||||
# logger.debug("Value white: %s", hex(rgb_array[50*400+261])) # This ought to be white
|
||||
# logger.debug("Value cyan: %s", hex(rgb_array[10*400+252])) # This ought to be cyan
|
||||
#
|
||||
# rgba_array = (0xFF000000 + ((rgb_array & 0x001F) << 11)) # Exclusively green?
|
||||
# rgba_array[10*400+36] = 0xFFFF0000
|
||||
# rgba_array[50*400+261] = 0xFFFF0000
|
||||
# rgba_array[10*400+252] = 0xFFFF0000
|
||||
|
||||
# At this point, the RGBA array is structured as 4 small images:
|
||||
# 13
|
||||
# 24
|
||||
# each of which represents the pixels in a differently structured larger image:
|
||||
# 12
|
||||
# 34
|
||||
# Let us unwrap.
|
||||
|
||||
unwrapped_array = np.empty(self.screenwidth*self.screenheight, dtype=np.uint32)
|
||||
for y in range(self.screenheight//2):
|
||||
for x in range(self.screenwidth//2):
|
||||
unwrapped_array[2 * x + 2 * y * self.screenwidth] = rgba_array[x + y * self.screenwidth]
|
||||
unwrapped_array[(2 * x) + 1 + 2 * y * self.screenwidth] = \
|
||||
rgba_array[x + (self.screenheight//2 + y) * self.screenwidth]
|
||||
unwrapped_array[2 * x + (2 * y + 1) * self.screenwidth] = \
|
||||
rgba_array[x + self.screenwidth//2 + y * self.screenwidth]
|
||||
unwrapped_array[(2 * x) + 1 + (2 * y + 1) * self.screenwidth] = \
|
||||
rgba_array[x + self.screenwidth//2 + (self.screenheight//2 + y) * self.screenwidth]
|
||||
|
||||
image = QtGui.QImage(unwrapped_array, self.screenwidth, self.screenheight, QtGui.QImage.Format_ARGB32)
|
||||
logger.debug("Captured screenshot")
|
||||
return QtGui.QPixmap(image)
|
||||
except serial.SerialException as exc:
|
||||
logger.exception("Exception while capturing screenshot: %s", exc)
|
||||
finally:
|
||||
self.app.serialLock.release()
|
||||
return QtGui.QPixmap()
|
||||
|
||||
|
||||
class Version:
|
||||
|
|
|
@ -0,0 +1,154 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import logging
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
|
||||
from NanoVNASaver.RFTools import Datapoint
|
||||
from NanoVNASaver.Marker import Marker
|
||||
from NanoVNASaver.Marker.Values import TYPES, default_label_ids
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MarkerSettingsWindow(QtWidgets.QWidget):
|
||||
exampleData11 = [Datapoint(123000000, 0.89, -0.11),
|
||||
Datapoint(123500000, 0.9, -0.1),
|
||||
Datapoint(124000000, 0.91, -0.95)]
|
||||
exampleData21 = [Datapoint(123000000, -0.25, 0.49),
|
||||
Datapoint(123456000, -0.3, 0.5),
|
||||
Datapoint(124000000, -0.2, 0.5)]
|
||||
|
||||
def __init__(self, app: QtWidgets.QWidget):
|
||||
super().__init__()
|
||||
self.app = app
|
||||
|
||||
self.setWindowTitle("Marker settings")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.cancelButtonClick)
|
||||
|
||||
self.exampleMarker = Marker("Example marker")
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
settings_group_box = QtWidgets.QGroupBox("Settings")
|
||||
settings_group_box_layout = QtWidgets.QFormLayout(settings_group_box)
|
||||
self.checkboxColouredMarker = QtWidgets.QCheckBox("Colored marker name")
|
||||
self.checkboxColouredMarker.setChecked(self.app.settings.value("ColoredMarkerNames", True, bool))
|
||||
self.checkboxColouredMarker.stateChanged.connect(self.updateMarker)
|
||||
settings_group_box_layout.addRow(self.checkboxColouredMarker)
|
||||
|
||||
fields_group_box = QtWidgets.QGroupBox("Displayed data")
|
||||
fields_group_box_layout = QtWidgets.QFormLayout(fields_group_box)
|
||||
|
||||
self.savedFieldSelection = self.app.settings.value(
|
||||
"MarkerFields", defaultValue=default_label_ids()
|
||||
)
|
||||
|
||||
if self.savedFieldSelection == "":
|
||||
self.savedFieldSelection = []
|
||||
|
||||
self.currentFieldSelection = self.savedFieldSelection[:]
|
||||
|
||||
self.active_labels_view = QtWidgets.QListView()
|
||||
self.update_displayed_data_form()
|
||||
|
||||
fields_group_box_layout.addRow(self.active_labels_view)
|
||||
|
||||
layout.addWidget(settings_group_box)
|
||||
layout.addWidget(fields_group_box)
|
||||
layout.addWidget(self.exampleMarker.getGroupBox())
|
||||
|
||||
btn_layout = QtWidgets.QHBoxLayout()
|
||||
layout.addLayout(btn_layout)
|
||||
btn_ok = QtWidgets.QPushButton("OK")
|
||||
btn_apply = QtWidgets.QPushButton("Apply")
|
||||
btn_default = QtWidgets.QPushButton("Defaults")
|
||||
btn_cancel = QtWidgets.QPushButton("Cancel")
|
||||
|
||||
btn_ok.clicked.connect(self.okButtonClick)
|
||||
btn_apply.clicked.connect(self.applyButtonClick)
|
||||
btn_default.clicked.connect(self.defaultButtonClick)
|
||||
btn_cancel.clicked.connect(self.cancelButtonClick)
|
||||
|
||||
btn_layout.addWidget(btn_ok)
|
||||
btn_layout.addWidget(btn_apply)
|
||||
btn_layout.addWidget(btn_default)
|
||||
btn_layout.addWidget(btn_cancel)
|
||||
|
||||
self.updateMarker()
|
||||
for m in self.app.markers:
|
||||
m.setFieldSelection(self.currentFieldSelection)
|
||||
m.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
|
||||
def updateMarker(self):
|
||||
self.exampleMarker.setFrequency(123456000)
|
||||
self.exampleMarker.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
self.exampleMarker.setFieldSelection(self.currentFieldSelection)
|
||||
self.exampleMarker.findLocation(self.exampleData11)
|
||||
self.exampleMarker.resetLabels()
|
||||
self.exampleMarker.updateLabels(self.exampleData11, self.exampleData21)
|
||||
|
||||
def updateField(self, field: QtGui.QStandardItem):
|
||||
if field.checkState() == QtCore.Qt.Checked:
|
||||
if not field.data() in self.currentFieldSelection:
|
||||
self.currentFieldSelection = []
|
||||
for i in range(self.model.rowCount()):
|
||||
field = self.model.item(i, 0)
|
||||
if field.checkState() == QtCore.Qt.Checked:
|
||||
self.currentFieldSelection.append(field.data())
|
||||
else:
|
||||
if field.data() in self.currentFieldSelection:
|
||||
self.currentFieldSelection.remove(field.data())
|
||||
self.updateMarker()
|
||||
|
||||
def applyButtonClick(self):
|
||||
self.savedFieldSelection = self.currentFieldSelection[:]
|
||||
self.app.settings.setValue("MarkerFields", self.savedFieldSelection)
|
||||
self.app.settings.setValue("ColoredMarkerNames", self.checkboxColouredMarker.isChecked())
|
||||
for m in self.app.markers:
|
||||
m.setFieldSelection(self.savedFieldSelection)
|
||||
m.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
|
||||
def okButtonClick(self):
|
||||
self.applyButtonClick()
|
||||
self.close()
|
||||
|
||||
def cancelButtonClick(self):
|
||||
self.currentFieldSelection = self.savedFieldSelection[:]
|
||||
self.update_displayed_data_form()
|
||||
self.updateMarker()
|
||||
self.close()
|
||||
|
||||
def defaultButtonClick(self):
|
||||
self.currentFieldSelection = default_label_ids()
|
||||
self.update_displayed_data_form()
|
||||
self.updateMarker()
|
||||
|
||||
def update_displayed_data_form(self):
|
||||
self.model = QtGui.QStandardItemModel()
|
||||
for label in TYPES:
|
||||
item = QtGui.QStandardItem(label.description)
|
||||
item.setData(label.label_id)
|
||||
item.setCheckable(True)
|
||||
item.setEditable(False)
|
||||
if label.label_id in self.currentFieldSelection:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
self.model.appendRow(item)
|
||||
self.active_labels_view.setModel(self.model)
|
||||
self.model.itemChanged.connect(self.updateField)
|
|
@ -0,0 +1,86 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import List, NamedTuple
|
||||
from NanoVNASaver.RFTools import Datapoint
|
||||
|
||||
|
||||
class Label(NamedTuple):
|
||||
label_id: str
|
||||
name: str
|
||||
description: str
|
||||
default_active: bool
|
||||
|
||||
|
||||
TYPES = (
|
||||
Label("actualfreq", "Frequency", "Actual frequency", True),
|
||||
Label("impedance", "Impedance", "Impedance", True),
|
||||
Label("admittance", "Admittance", "Admittance", False),
|
||||
Label("serr", "Series R", "Series R", False),
|
||||
Label("serlc", "Series X", "Series equivalent L/C", False),
|
||||
Label("serl", "Series L", "Series equivalent L", True),
|
||||
Label("serc", "Series C", "Series equivalent C", True),
|
||||
Label("parr", "Parallel R", "Parallel R", True),
|
||||
Label("parlc", "Parallel X", "Parallel equivalent L/C", True),
|
||||
Label("parl", "Parallel L", "Parallel equivalent L", False),
|
||||
Label("parc", "Parallel C", "Parallel equivalent C", False),
|
||||
Label("vswr", "VSWR", "VSWR", True),
|
||||
Label("returnloss", "Return loss", "Return loss", True),
|
||||
Label("s11mag", "|S11|", "S11 Magnitude", False),
|
||||
Label("s11q", "Quality factor", "S11 Quality factor", True),
|
||||
Label("s11z", "S11 |Z|", "S11 Z Magnitude", False),
|
||||
Label("s11phase", "S11 Phase", "S11 Phase", True),
|
||||
Label("s11polar", "S11 Polar", "S11 Polar", False),
|
||||
Label("s11groupdelay", "S11 Group Delay", "S11 Group Delay", False),
|
||||
Label("s21gain", "S21 Gain", "S21 Gain", True),
|
||||
Label("s21mag", "|S21|", "S21 Magnitude", False),
|
||||
Label("s21phase", "S21 Phase", "S21 Phase", True),
|
||||
Label("s21polar", "S21 Polar", "S21 Polar", False),
|
||||
Label("s21groupdelay", "S21 Group Delay", "S21 Group Delay", False),
|
||||
)
|
||||
|
||||
|
||||
def default_label_ids() -> str:
|
||||
return [l.label_id for l in TYPES if l.default_active]
|
||||
|
||||
|
||||
class Value():
|
||||
"""Contains the data area to calculate marker values from"""
|
||||
def __init__(self, freq: int = 0,
|
||||
s11data: List[Datapoint] = None,
|
||||
s21data: List[Datapoint] = None):
|
||||
self.freq = freq
|
||||
self.s11data = [] if s11data is None else s11data[:]
|
||||
self.s21data = [] if s21data is None else s21data[:]
|
||||
|
||||
def store(self, index: int,
|
||||
s11data: List[Datapoint],
|
||||
s21data: List[Datapoint]):
|
||||
# handle boundaries
|
||||
if index == 0:
|
||||
index = 1
|
||||
s11data = [s11data[0], ] + s11data
|
||||
if s21data:
|
||||
s21data = [s21data[0], ] + s21data
|
||||
if index == len(s11data):
|
||||
s11data = s11data + [s11data[-1], ]
|
||||
if s21data:
|
||||
s21data = s21data + [s21data[-1], ]
|
||||
self.freq = s11data[1].freq
|
||||
self.s11data = s11data[index-1:index+2]
|
||||
if s21data:
|
||||
self.s21data = s21data[index-1:index+2]
|
|
@ -21,86 +21,84 @@ from PyQt5 import QtGui, QtWidgets, QtCore
|
|||
from PyQt5.QtCore import pyqtSignal
|
||||
|
||||
from NanoVNASaver import RFTools
|
||||
from NanoVNASaver.Formatting import format_frequency, format_capacitance, format_inductance, format_complex_imp, \
|
||||
format_resistance, format_vswr, format_phase, format_q_factor, format_gain, format_group_delay
|
||||
|
||||
from NanoVNASaver.Formatting import (
|
||||
format_capacitance,
|
||||
format_complex_imp,
|
||||
format_frequency,
|
||||
format_gain,
|
||||
format_group_delay,
|
||||
format_inductance,
|
||||
format_magnitude,
|
||||
format_phase,
|
||||
format_q_factor,
|
||||
format_resistance,
|
||||
format_vswr,
|
||||
)
|
||||
from NanoVNASaver.Inputs import MarkerFrequencyInputWidget as FrequencyInput
|
||||
from NanoVNASaver.Marker.Values import TYPES, Value, default_label_ids
|
||||
|
||||
COLORS = (
|
||||
QtGui.QColor(QtCore.Qt.darkGray),
|
||||
QtGui.QColor(255, 0, 0),
|
||||
QtGui.QColor(0, 255, 0),
|
||||
QtGui.QColor(0, 0, 255),
|
||||
QtGui.QColor(0, 255, 255),
|
||||
QtGui.QColor(255, 0, 255),
|
||||
QtGui.QColor(255, 255, 0),
|
||||
)
|
||||
|
||||
|
||||
class Marker(QtCore.QObject):
|
||||
name = "Marker"
|
||||
frequency = 0
|
||||
class MarkerLabel(QtWidgets.QLabel):
|
||||
def __init__(self, name):
|
||||
super().__init__("")
|
||||
self.name = name
|
||||
|
||||
|
||||
class Marker(QtCore.QObject, Value):
|
||||
_instances = 0
|
||||
color: QtGui.QColor = QtGui.QColor()
|
||||
coloredText = True
|
||||
location = -1
|
||||
|
||||
returnloss_is_positive = False
|
||||
|
||||
updated = pyqtSignal(object)
|
||||
active_labels = []
|
||||
|
||||
fieldSelection = []
|
||||
@classmethod
|
||||
def count(cls):
|
||||
return cls._instances
|
||||
|
||||
def __init__(self, name, initialColor, frequency=""):
|
||||
super().__init__()
|
||||
def __init__(self, name: str = "", qsettings: QtCore.QSettings = None):
|
||||
super(QtCore.QObject, self).__init__()
|
||||
self.name = name
|
||||
self.qsettings = qsettings
|
||||
self.name = name
|
||||
self.color = QtGui.QColor()
|
||||
self.index = 0
|
||||
|
||||
self.frequency = RFTools.RFTools.parseFrequency(frequency)
|
||||
if self.qsettings:
|
||||
Marker._instances += 1
|
||||
Marker.active_labels = self.qsettings.value(
|
||||
"MarkerFields", defaultValue=default_label_ids())
|
||||
self.index = Marker._instances
|
||||
|
||||
if not self.name:
|
||||
self.name = f"Marker {Marker._instances}"
|
||||
|
||||
# self.freq = RFTools.RFTools.parseFrequency(frequency)
|
||||
|
||||
self.frequencyInput = FrequencyInput()
|
||||
self.frequencyInput.setAlignment(QtCore.Qt.AlignRight)
|
||||
self.frequencyInput.textEdited.connect(self.setFrequency)
|
||||
|
||||
################################################################################################################
|
||||
###############################################################
|
||||
# Data display labels
|
||||
################################################################################################################
|
||||
###############################################################
|
||||
|
||||
self.frequency_label = QtWidgets.QLabel("")
|
||||
self.frequency_label.setMinimumWidth(100)
|
||||
self.impedance_label = QtWidgets.QLabel("")
|
||||
self.admittance_label = QtWidgets.QLabel("")
|
||||
self.parallel_r_label = QtWidgets.QLabel("")
|
||||
self.parallel_x_label = QtWidgets.QLabel("")
|
||||
self.parallel_c_label = QtWidgets.QLabel("")
|
||||
self.parallel_l_label = QtWidgets.QLabel("")
|
||||
self.returnloss_label = QtWidgets.QLabel("")
|
||||
self.returnloss_label.setMinimumWidth(80)
|
||||
self.vswr_label = QtWidgets.QLabel("")
|
||||
self.series_r_label = QtWidgets.QLabel("")
|
||||
self.series_x_label = QtWidgets.QLabel("")
|
||||
self.inductance_label = QtWidgets.QLabel("")
|
||||
self.capacitance_label = QtWidgets.QLabel("")
|
||||
self.gain_label = QtWidgets.QLabel("")
|
||||
self.s11_phase_label = QtWidgets.QLabel("")
|
||||
self.s21_phase_label = QtWidgets.QLabel("")
|
||||
self.s11_group_delay_label = QtWidgets.QLabel("")
|
||||
self.s21_group_delay_label = QtWidgets.QLabel("")
|
||||
self.s11_polar_label = QtWidgets.QLabel("")
|
||||
self.s21_polar_label = QtWidgets.QLabel("")
|
||||
self.quality_factor_label = QtWidgets.QLabel("")
|
||||
|
||||
self.fields = {
|
||||
"actualfreq": ("Frequency:", self.frequency_label),
|
||||
"impedance": ("Impedance:", self.impedance_label),
|
||||
"admittance": ("Admittance:", self.admittance_label),
|
||||
"s11polar": ("S11 Polar:", self.s11_polar_label),
|
||||
"s21polar": ("S21 Polar:", self.s21_polar_label),
|
||||
"serr": ("Series R:", self.series_r_label),
|
||||
"serl": ("Series L:", self.inductance_label),
|
||||
"serc": ("Series C:", self.capacitance_label),
|
||||
"serlc": ("Series X:", self.series_x_label),
|
||||
"parr": ("Parallel R:", self.parallel_r_label),
|
||||
"parc": ("Parallel C:", self.parallel_c_label),
|
||||
"parl": ("Parallel L:", self.parallel_l_label),
|
||||
"parlc": ("Parallel X:", self.parallel_x_label),
|
||||
"returnloss": ("Return loss:", self.returnloss_label),
|
||||
"vswr": ("VSWR:", self.vswr_label),
|
||||
"s11q": ("Quality factor:", self.quality_factor_label),
|
||||
"s11phase": ("S11 Phase:", self.s11_phase_label),
|
||||
"s11groupdelay": ("S11 Group Delay:", self.s11_group_delay_label),
|
||||
"s21gain": ("S21 Gain:", self.gain_label),
|
||||
"s21phase": ("S21 Phase:", self.s21_phase_label),
|
||||
"s21groupdelay": ("S21 Group Delay:", self.s21_group_delay_label),
|
||||
}
|
||||
self.label = {}
|
||||
for l in TYPES:
|
||||
self.label[l.label_id] = MarkerLabel(l.name)
|
||||
self.label['actualfreq'].setMinimumWidth(100)
|
||||
self.label['returnloss'].setMinimumWidth(80)
|
||||
|
||||
###############################################################
|
||||
# Marker control layout
|
||||
|
@ -127,11 +125,19 @@ class Marker(QtCore.QObject):
|
|||
self.group_box.setMaximumWidth(340)
|
||||
box_layout = QtWidgets.QHBoxLayout(self.group_box)
|
||||
|
||||
self.setColor(initialColor)
|
||||
try:
|
||||
self.setColor(
|
||||
self.qsettings.value(
|
||||
f"Marker{self.count()}Color", COLORS[self.count()]))
|
||||
except AttributeError: # happens when qsettings == None
|
||||
self.setColor(COLORS[1])
|
||||
except IndexError:
|
||||
self.setColor(COLORS[0])
|
||||
|
||||
line = QtWidgets.QFrame()
|
||||
line.setFrameShape(QtWidgets.QFrame.VLine)
|
||||
|
||||
# line only if more then 3 selected
|
||||
self.left_form = QtWidgets.QFormLayout()
|
||||
self.right_form = QtWidgets.QFormLayout()
|
||||
box_layout.addLayout(self.left_form)
|
||||
|
@ -140,13 +146,27 @@ class Marker(QtCore.QObject):
|
|||
|
||||
self.buildForm()
|
||||
|
||||
def __del__(self):
|
||||
if self.qsettings:
|
||||
Marker._instances -= 1
|
||||
|
||||
def _add_active_labels(self, label_id, form):
|
||||
if label_id in self.label:
|
||||
form.addRow(
|
||||
f"{self.label[label_id].name}:", self.label[label_id])
|
||||
self.label[label_id].show()
|
||||
|
||||
def _size_str(self) -> str:
|
||||
return str(self.group_box.font().pointSize())
|
||||
|
||||
def update_settings(self):
|
||||
self.qsettings.setValue(f"Marker{self.index}Color", self.color)
|
||||
|
||||
def setScale(self, scale):
|
||||
self.group_box.setMaximumWidth(int(340 * scale))
|
||||
self.frequency_label.setMinimumWidth(int(100 * scale))
|
||||
self.returnloss_label.setMinimumWidth(int(80 * scale))
|
||||
self.label['actualfreq'].setMinimumWidth(int(100 * scale))
|
||||
self.label['actualfreq'].setMinimumWidth(int(100 * scale))
|
||||
self.label['returnloss'].setMinimumWidth(int(80 * scale))
|
||||
if self.coloredText:
|
||||
color_string = QtCore.QVariant(self.color)
|
||||
color_string.convert(QtCore.QVariant.String)
|
||||
|
@ -170,34 +190,25 @@ class Marker(QtCore.QObject):
|
|||
old_row.fieldItem.widget().hide()
|
||||
old_row.labelItem.widget().hide()
|
||||
|
||||
if len(self.fieldSelection) <= 3:
|
||||
for field in self.fieldSelection:
|
||||
if field in self.fields:
|
||||
label, value = self.fields[field]
|
||||
self.left_form.addRow(label, value)
|
||||
value.show()
|
||||
if len(self.active_labels) <= 3:
|
||||
for label_id in self.active_labels:
|
||||
self._add_active_labels(label_id, self.left_form)
|
||||
else:
|
||||
left_half = math.ceil(len(self.fieldSelection)/2)
|
||||
right_half = len(self.fieldSelection)
|
||||
left_half = math.ceil(len(self.active_labels)/2)
|
||||
right_half = len(self.active_labels)
|
||||
for i in range(left_half):
|
||||
field = self.fieldSelection[i]
|
||||
if field in self.fields:
|
||||
label, value = self.fields[field]
|
||||
self.left_form.addRow(label, value)
|
||||
value.show()
|
||||
label_id = self.active_labels[i]
|
||||
self._add_active_labels(label_id, self.left_form)
|
||||
for i in range(left_half, right_half):
|
||||
field = self.fieldSelection[i]
|
||||
if field in self.fields:
|
||||
label, value = self.fields[field]
|
||||
self.right_form.addRow(label, value)
|
||||
value.show()
|
||||
label_id = self.active_labels[i]
|
||||
self._add_active_labels(label_id, self.right_form)
|
||||
|
||||
def setFrequency(self, frequency):
|
||||
self.frequency = RFTools.RFTools.parseFrequency(frequency)
|
||||
self.freq = RFTools.RFTools.parseFrequency(frequency)
|
||||
self.updated.emit(self)
|
||||
|
||||
def setFieldSelection(self, fields):
|
||||
self.fieldSelection: List[str] = fields.copy()
|
||||
self.active_labels = fields[:]
|
||||
self.buildForm()
|
||||
|
||||
def setColor(self, color):
|
||||
|
@ -229,9 +240,6 @@ class Marker(QtCore.QObject):
|
|||
self.location = -1
|
||||
self.frequencyInput.nextFrequency = -1
|
||||
self.frequencyInput.previousFrequency = -1
|
||||
if self.frequency <= 0:
|
||||
# No frequency set for this marker
|
||||
return
|
||||
datasize = len(data)
|
||||
if datasize == 0:
|
||||
# Set the frequency before loading any data
|
||||
|
@ -243,13 +251,14 @@ class Marker(QtCore.QObject):
|
|||
upper_stepsize = data[-1].freq - data[-2].freq
|
||||
|
||||
# We are outside the bounds of the data, so we can't put in a marker
|
||||
if self.frequency + lower_stepsize/2 < min_freq or self.frequency - upper_stepsize/2 > max_freq:
|
||||
if (self.freq + lower_stepsize/2 < min_freq or
|
||||
self.freq - upper_stepsize/2 > max_freq):
|
||||
return
|
||||
|
||||
min_distance = max_freq
|
||||
for i, item in enumerate(data):
|
||||
if abs(item.freq - self.frequency) <= min_distance:
|
||||
min_distance = abs(item.freq - self.frequency)
|
||||
if abs(item.freq - self.freq) <= min_distance:
|
||||
min_distance = abs(item.freq - self.freq)
|
||||
else:
|
||||
# We have now started moving away from the nearest point
|
||||
self.location = i-1
|
||||
|
@ -266,33 +275,16 @@ class Marker(QtCore.QObject):
|
|||
return self.group_box
|
||||
|
||||
def resetLabels(self):
|
||||
self.frequency_label.setText("")
|
||||
self.impedance_label.setText("")
|
||||
self.admittance_label.setText("")
|
||||
self.s11_polar_label.setText("")
|
||||
self.s21_polar_label.setText("")
|
||||
self.parallel_r_label.setText("")
|
||||
self.parallel_x_label.setText("")
|
||||
self.parallel_l_label.setText("")
|
||||
self.parallel_c_label.setText("")
|
||||
self.series_x_label.setText("")
|
||||
self.series_r_label.setText("")
|
||||
self.inductance_label.setText("")
|
||||
self.capacitance_label.setText("")
|
||||
self.vswr_label.setText("")
|
||||
self.returnloss_label.setText("")
|
||||
self.gain_label.setText("")
|
||||
self.s11_phase_label.setText("")
|
||||
self.s21_phase_label.setText("")
|
||||
self.s11_group_delay_label.setText("")
|
||||
self.s21_group_delay_label.setText("")
|
||||
self.quality_factor_label.setText("")
|
||||
for v in self.label.values():
|
||||
v.setText("")
|
||||
|
||||
def updateLabels(self,
|
||||
s11data: List[RFTools.Datapoint],
|
||||
s21data: List[RFTools.Datapoint]):
|
||||
if self.location == -1:
|
||||
return
|
||||
self.store(self.location, s11data, s21data)
|
||||
|
||||
s11 = s11data[self.location]
|
||||
|
||||
imp = s11.impedance()
|
||||
|
@ -313,35 +305,35 @@ class Marker(QtCore.QObject):
|
|||
else:
|
||||
x_p_str = ind_p_str
|
||||
|
||||
self.frequency_label.setText(format_frequency(s11.freq))
|
||||
|
||||
self.impedance_label.setText(format_complex_imp(imp))
|
||||
self.series_r_label.setText(format_resistance(imp.real))
|
||||
self.series_x_label.setText(x_str)
|
||||
self.capacitance_label.setText(cap_str)
|
||||
self.inductance_label.setText(ind_str)
|
||||
|
||||
self.admittance_label.setText(format_complex_imp(imp_p))
|
||||
self.parallel_r_label.setText(format_resistance(imp_p.real))
|
||||
self.parallel_x_label.setText(x_p_str)
|
||||
self.parallel_c_label.setText(cap_p_str)
|
||||
self.parallel_l_label.setText(ind_p_str)
|
||||
|
||||
self.vswr_label.setText(format_vswr(s11.vswr))
|
||||
self.s11_phase_label.setText(format_phase(s11.phase))
|
||||
self.quality_factor_label.setText(format_q_factor(s11.qFactor()))
|
||||
|
||||
self.returnloss_label.setText(format_gain(s11.gain, self.returnloss_is_positive))
|
||||
self.s11_group_delay_label.setText(format_group_delay(RFTools.groupDelay(s11data, self.location)))
|
||||
|
||||
self.s11_polar_label.setText(str(round(abs(s11.z), 2)) + "∠" + format_phase(s11.phase))
|
||||
self.label['actualfreq'].setText(format_frequency(s11.freq))
|
||||
self.label['admittance'].setText(format_complex_imp(imp_p))
|
||||
self.label['impedance'].setText(format_complex_imp(imp))
|
||||
self.label['parc'].setText(cap_p_str)
|
||||
self.label['parl'].setText(ind_p_str)
|
||||
self.label['parlc'].setText(x_p_str)
|
||||
self.label['parr'].setText(format_resistance(imp_p.real))
|
||||
self.label['returnloss'].setText(
|
||||
format_gain(s11.gain, self.returnloss_is_positive))
|
||||
self.label['s11groupdelay'].setText(
|
||||
format_group_delay(RFTools.groupDelay(s11data, self.location)))
|
||||
self.label['s11mag'].setText(format_magnitude(abs(s11.z)))
|
||||
self.label['s11phase'].setText(format_phase(s11.phase))
|
||||
self.label['s11polar'].setText(
|
||||
str(round(abs(s11.z), 2)) + "∠" + format_phase(s11.phase))
|
||||
self.label['s11q'].setText(format_q_factor(s11.qFactor()))
|
||||
self.label['s11z'].setText(format_resistance(abs(imp)))
|
||||
self.label['serc'].setText(cap_str)
|
||||
self.label['serl'].setText(ind_str)
|
||||
self.label['serlc'].setText(x_str)
|
||||
self.label['serr'].setText(format_resistance(imp.real))
|
||||
self.label['vswr'].setText(format_vswr(s11.vswr))
|
||||
|
||||
if len(s21data) == len(s11data):
|
||||
|
||||
s21 = s21data[self.location]
|
||||
|
||||
self.s21_phase_label.setText(format_phase(s21.phase))
|
||||
self.gain_label.setText(format_gain(s21.gain))
|
||||
self.s21_group_delay_label.setText(format_group_delay(RFTools.groupDelay(s21data, self.location) / 2))
|
||||
self.s21_polar_label.setText(str(round(abs(s21.z), 2)) + "∠" + format_phase(s21.phase))
|
||||
|
||||
self.label['s21gain'].setText(format_gain(s21.gain))
|
||||
self.label['s21groupdelay'].setText(
|
||||
format_group_delay(RFTools.groupDelay(s21data, self.location) / 2))
|
||||
self.label['s21mag'].setText(format_magnitude(abs(s21.z)))
|
||||
self.label['s21phase'].setText(format_phase(s21.phase))
|
||||
self.label['s21polar'].setText(
|
||||
str(round(abs(s21.z), 2)) + "∠" + format_phase(s21.phase))
|
|
@ -0,0 +1,3 @@
|
|||
from .Widget import Marker # noqa
|
||||
from .Settings import MarkerSettingsWindow # noqa
|
||||
from .Values import Value, default_label_ids # noqa
|
|
@ -35,7 +35,7 @@ from .Chart import Chart, PhaseChart, VSWRChart, PolarChart, SmithChart, LogMagC
|
|||
GroupDelayChart, CapacitanceChart, InductanceChart
|
||||
from .Calibration import CalibrationWindow, Calibration
|
||||
from .Inputs import FrequencyInputWidget
|
||||
from .Marker import Marker
|
||||
from .Marker import Marker, MarkerSettingsWindow
|
||||
from .SweepWorker import SweepWorker
|
||||
from .Touchstone import Touchstone
|
||||
from .Analysis import Analysis, LowPassAnalysis, HighPassAnalysis, BandPassAnalysis, BandStopAnalysis, \
|
||||
|
@ -50,13 +50,6 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
class NanoVNASaver(QtWidgets.QWidget):
|
||||
version = ver
|
||||
default_marker_colors = [QtGui.QColor(255, 0, 0),
|
||||
QtGui.QColor(0, 255, 0),
|
||||
QtGui.QColor(0, 0, 255),
|
||||
QtGui.QColor(0, 255, 255),
|
||||
QtGui.QColor(255, 0, 255),
|
||||
QtGui.QColor(255, 255, 0)]
|
||||
|
||||
dataAvailable = QtCore.pyqtSignal()
|
||||
scaleFactor = 1
|
||||
|
||||
|
@ -303,12 +296,7 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
|
||||
marker_count = max(self.settings.value("MarkerCount", 3, int), 1)
|
||||
for i in range(marker_count):
|
||||
if i < len(self.default_marker_colors):
|
||||
default_color = self.default_marker_colors[i]
|
||||
else:
|
||||
default_color = QtGui.QColor(QtCore.Qt.darkGray)
|
||||
color = self.settings.value("Marker" + str(i+1) + "Color", default_color)
|
||||
marker = Marker("Marker " + str(i+1), color)
|
||||
marker = Marker("", self.settings)
|
||||
marker.updated.connect(self.markerUpdated)
|
||||
label, layout = marker.getRow()
|
||||
self.marker_control_layout.addRow(label, layout)
|
||||
|
@ -493,11 +481,11 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
save_file_control_box.setMaximumWidth(300)
|
||||
save_file_control_layout = QtWidgets.QFormLayout(save_file_control_box)
|
||||
|
||||
btn_export_file = QtWidgets.QPushButton("Save file (S1P)")
|
||||
btn_export_file = QtWidgets.QPushButton("Save 1-Port file (S1P)")
|
||||
btn_export_file.clicked.connect(self.exportFileS1P)
|
||||
save_file_control_layout.addRow(btn_export_file)
|
||||
|
||||
btn_export_file = QtWidgets.QPushButton("Save file (S2P)")
|
||||
btn_export_file = QtWidgets.QPushButton("Save 2-Port file (S2P)")
|
||||
btn_export_file.clicked.connect(self.exportFileS2P)
|
||||
save_file_control_layout.addRow(btn_export_file)
|
||||
|
||||
|
@ -565,7 +553,7 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
|
||||
filedialog = QtWidgets.QFileDialog(self)
|
||||
filedialog.setDefaultSuffix("s1p")
|
||||
filedialog.setNameFilter("Touchstone Files (*.s1p *.s2p);;All files (*.*)")
|
||||
filedialog.setNameFilter("Touchstone 1-Port Files (*.s1p);;All files (*.*)")
|
||||
filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
|
||||
selected = filedialog.exec()
|
||||
if selected:
|
||||
|
@ -601,7 +589,7 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
|
||||
filedialog = QtWidgets.QFileDialog(self)
|
||||
filedialog.setDefaultSuffix("s2p")
|
||||
filedialog.setNameFilter("Touchstone Files (*.s1p *.s2p);;All files (*.*)")
|
||||
filedialog.setNameFilter("Touchstone 2-Port Files (*.s2p);;All files (*.*)")
|
||||
filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave)
|
||||
selected = filedialog.exec()
|
||||
if selected:
|
||||
|
@ -991,9 +979,9 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
self.worker.stopped = True
|
||||
self.settings.setValue("MarkerCount", len(self.markers))
|
||||
for i in range(len(self.markers)):
|
||||
self.settings.setValue("Marker" + str(i+1) + "Color", self.markers[i].color)
|
||||
self.settings.setValue("MarkerCount", Marker.count())
|
||||
for marker in self.markers:
|
||||
marker.update_settings()
|
||||
|
||||
self.settings.setValue("WindowHeight", self.height())
|
||||
self.settings.setValue("WindowWidth", self.width())
|
||||
|
@ -1671,38 +1659,27 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
|
|||
QtWidgets.QApplication.setActiveWindow(self.marker_window)
|
||||
|
||||
def addMarker(self):
|
||||
marker_count = len(self.app.markers)
|
||||
if marker_count < 6:
|
||||
color = NanoVNASaver.default_marker_colors[marker_count]
|
||||
else:
|
||||
color = QtGui.QColor(QtCore.Qt.darkGray)
|
||||
new_marker = Marker("Marker " + str(marker_count+1), color)
|
||||
new_marker.setColoredText(self.app.settings.value("ColoredMarkerNames", True, bool))
|
||||
new_marker.setFieldSelection(self.app.settings.value("MarkerFields",
|
||||
defaultValue=self.marker_window.defaultValue))
|
||||
new_marker = Marker("", self.app.settings)
|
||||
new_marker.setScale(self.app.scaleFactor)
|
||||
self.app.markers.append(new_marker)
|
||||
self.app.marker_data_layout.addWidget(new_marker.getGroupBox())
|
||||
|
||||
new_marker.updated.connect(self.app.markerUpdated)
|
||||
label, layout = new_marker.getRow()
|
||||
self.app.marker_control_layout.insertRow(marker_count, label, layout)
|
||||
if marker_count == 0:
|
||||
new_marker.isMouseControlledRadioButton.setChecked(True)
|
||||
|
||||
self.app.marker_control_layout.insertRow(Marker.count() - 1, label, layout)
|
||||
self.btn_remove_marker.setDisabled(False)
|
||||
|
||||
def removeMarker(self):
|
||||
# keep at least one marker
|
||||
if len(self.app.markers) <= 1:
|
||||
if Marker.count() <= 1:
|
||||
return
|
||||
if len(self.app.markers) == 2:
|
||||
if Marker.count() == 2:
|
||||
self.btn_remove_marker.setDisabled(True)
|
||||
last_marker = self.app.markers.pop()
|
||||
|
||||
last_marker.updated.disconnect(self.app.markerUpdated)
|
||||
self.app.marker_data_layout.removeWidget(last_marker.getGroupBox())
|
||||
self.app.marker_control_layout.removeRow(len(self.app.markers))
|
||||
self.app.marker_control_layout.removeRow(Marker.count()-1)
|
||||
last_marker.getGroupBox().hide()
|
||||
last_marker.getGroupBox().destroy()
|
||||
label, layout = last_marker.getRow()
|
||||
|
@ -2409,186 +2386,6 @@ class AnalysisWindow(QtWidgets.QWidget):
|
|||
self.app.dataAvailable.disconnect(self.runAnalysis)
|
||||
|
||||
|
||||
class MarkerSettingsWindow(QtWidgets.QWidget):
|
||||
exampleData11 = [Datapoint(123000000, 0.89, -0.11),
|
||||
Datapoint(123500000, 0.9, -0.1),
|
||||
Datapoint(124000000, 0.91, -0.95)]
|
||||
exampleData21 = [Datapoint(123000000, -0.25, 0.49),
|
||||
Datapoint(123456000, -0.3, 0.5),
|
||||
Datapoint(124000000, -0.2, 0.5)]
|
||||
|
||||
fieldList = {"actualfreq": "Actual frequency",
|
||||
"impedance": "Impedance",
|
||||
"admittance": "Admittance",
|
||||
"s11polar": "S11 Polar Form",
|
||||
"s21polar": "S21 Polar Form",
|
||||
"serr": "Series R",
|
||||
"serlc": "Series equivalent L/C",
|
||||
"serl": "Series equivalent L",
|
||||
"serc": "Series equivalent C",
|
||||
"parr": "Parallel R",
|
||||
"parlc": "Parallel equivalent L/C",
|
||||
"parl": "Parallel equivalent L",
|
||||
"parc": "Parallel equivalent C",
|
||||
"vswr": "VSWR",
|
||||
"returnloss": "Return loss",
|
||||
"s11q": "S11 Quality factor",
|
||||
"s11phase": "S11 Phase",
|
||||
"s11groupdelay": "S11 Group Delay",
|
||||
"s21gain": "S21 Gain",
|
||||
"s21phase": "S21 Phase",
|
||||
"s21groupdelay": "S21 Group Delay",
|
||||
}
|
||||
|
||||
defaultValue = ["actualfreq",
|
||||
"impedance",
|
||||
"serl",
|
||||
"serc",
|
||||
"parr",
|
||||
"parlc",
|
||||
"vswr",
|
||||
"returnloss",
|
||||
"s11q",
|
||||
"s11phase",
|
||||
"s21gain",
|
||||
"s21phase"
|
||||
]
|
||||
|
||||
def __init__(self, app: NanoVNASaver):
|
||||
super().__init__()
|
||||
self.app = app
|
||||
|
||||
self.setWindowTitle("Marker settings")
|
||||
self.setWindowIcon(self.app.icon)
|
||||
|
||||
shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.cancelButtonClick)
|
||||
|
||||
if len(self.app.markers) > 0:
|
||||
color = self.app.markers[0].color
|
||||
else:
|
||||
color = self.app.default_marker_colors[0]
|
||||
|
||||
self.exampleMarker = Marker("Example marker", initialColor=color, frequency="123456000")
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
settings_group_box = QtWidgets.QGroupBox("Settings")
|
||||
settings_group_box_layout = QtWidgets.QFormLayout(settings_group_box)
|
||||
self.checkboxColouredMarker = QtWidgets.QCheckBox("Colored marker name")
|
||||
self.checkboxColouredMarker.setChecked(self.app.settings.value("ColoredMarkerNames", True, bool))
|
||||
self.checkboxColouredMarker.stateChanged.connect(self.updateMarker)
|
||||
settings_group_box_layout.addRow(self.checkboxColouredMarker)
|
||||
|
||||
fields_group_box = QtWidgets.QGroupBox("Displayed data")
|
||||
fields_group_box_layout = QtWidgets.QFormLayout(fields_group_box)
|
||||
|
||||
self.savedFieldSelection = self.app.settings.value("MarkerFields", defaultValue=self.defaultValue)
|
||||
|
||||
if self.savedFieldSelection == "":
|
||||
self.savedFieldSelection = []
|
||||
|
||||
self.currentFieldSelection = self.savedFieldSelection.copy()
|
||||
|
||||
self.fieldSelectionView = QtWidgets.QListView()
|
||||
self.model = QtGui.QStandardItemModel()
|
||||
for field in self.fieldList:
|
||||
item = QtGui.QStandardItem(self.fieldList[field])
|
||||
item.setData(field)
|
||||
item.setCheckable(True)
|
||||
item.setEditable(False)
|
||||
if field in self.currentFieldSelection:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
self.model.appendRow(item)
|
||||
self.fieldSelectionView.setModel(self.model)
|
||||
|
||||
self.model.itemChanged.connect(self.updateField)
|
||||
|
||||
fields_group_box_layout.addRow(self.fieldSelectionView)
|
||||
|
||||
layout.addWidget(settings_group_box)
|
||||
layout.addWidget(fields_group_box)
|
||||
layout.addWidget(self.exampleMarker.getGroupBox())
|
||||
|
||||
btn_layout = QtWidgets.QHBoxLayout()
|
||||
layout.addLayout(btn_layout)
|
||||
btn_ok = QtWidgets.QPushButton("OK")
|
||||
btn_apply = QtWidgets.QPushButton("Apply")
|
||||
btn_default = QtWidgets.QPushButton("Defaults")
|
||||
btn_cancel = QtWidgets.QPushButton("Cancel")
|
||||
|
||||
btn_ok.clicked.connect(self.okButtonClick)
|
||||
btn_apply.clicked.connect(self.applyButtonClick)
|
||||
btn_default.clicked.connect(self.defaultButtonClick)
|
||||
btn_cancel.clicked.connect(self.cancelButtonClick)
|
||||
|
||||
btn_layout.addWidget(btn_ok)
|
||||
btn_layout.addWidget(btn_apply)
|
||||
btn_layout.addWidget(btn_default)
|
||||
btn_layout.addWidget(btn_cancel)
|
||||
|
||||
self.updateMarker()
|
||||
for m in self.app.markers:
|
||||
m.setFieldSelection(self.currentFieldSelection)
|
||||
m.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
|
||||
def updateMarker(self):
|
||||
self.exampleMarker.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
self.exampleMarker.setFieldSelection(self.currentFieldSelection)
|
||||
self.exampleMarker.findLocation(self.exampleData11)
|
||||
self.exampleMarker.resetLabels()
|
||||
self.exampleMarker.updateLabels(self.exampleData11, self.exampleData21)
|
||||
|
||||
def updateField(self, field: QtGui.QStandardItem):
|
||||
if field.checkState() == QtCore.Qt.Checked:
|
||||
if not field.data() in self.currentFieldSelection:
|
||||
self.currentFieldSelection = []
|
||||
for i in range(self.model.rowCount()):
|
||||
field = self.model.item(i, 0)
|
||||
if field.checkState() == QtCore.Qt.Checked:
|
||||
self.currentFieldSelection.append(field.data())
|
||||
else:
|
||||
if field.data() in self.currentFieldSelection:
|
||||
self.currentFieldSelection.remove(field.data())
|
||||
self.updateMarker()
|
||||
|
||||
def applyButtonClick(self):
|
||||
self.savedFieldSelection = self.currentFieldSelection.copy()
|
||||
self.app.settings.setValue("MarkerFields", self.savedFieldSelection)
|
||||
self.app.settings.setValue("ColoredMarkerNames", self.checkboxColouredMarker.isChecked())
|
||||
for m in self.app.markers:
|
||||
m.setFieldSelection(self.savedFieldSelection)
|
||||
m.setColoredText(self.checkboxColouredMarker.isChecked())
|
||||
|
||||
def okButtonClick(self):
|
||||
self.applyButtonClick()
|
||||
self.close()
|
||||
|
||||
def cancelButtonClick(self):
|
||||
self.currentFieldSelection = self.savedFieldSelection.copy()
|
||||
self.resetModel()
|
||||
self.updateMarker()
|
||||
self.close()
|
||||
|
||||
def defaultButtonClick(self):
|
||||
self.currentFieldSelection = self.defaultValue.copy()
|
||||
self.resetModel()
|
||||
self.updateMarker()
|
||||
|
||||
def resetModel(self):
|
||||
self.model = QtGui.QStandardItemModel()
|
||||
for field in self.fieldList:
|
||||
item = QtGui.QStandardItem(self.fieldList[field])
|
||||
item.setData(field)
|
||||
item.setCheckable(True)
|
||||
item.setEditable(False)
|
||||
if field in self.currentFieldSelection:
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
self.model.appendRow(item)
|
||||
self.fieldSelectionView.setModel(self.model)
|
||||
self.model.itemChanged.connect(self.updateField)
|
||||
|
||||
|
||||
class DeviceSettingsWindow(QtWidgets.QWidget):
|
||||
def __init__(self, app: NanoVNASaver):
|
||||
super().__init__()
|
||||
|
|
|
@ -38,20 +38,15 @@ def parallel_to_serial(z: complex) -> complex:
|
|||
def serial_to_parallel(z: complex) -> complex:
|
||||
"""Convert serial impedance to parallel impedance equivalent"""
|
||||
z_sq_sum = z.real ** 2 + z.imag ** 2
|
||||
if z.real != 0 and z.imag != 0:
|
||||
return complex(z_sq_sum / z.real, z_sq_sum / z.imag)
|
||||
elif z.real != 0 and z_sq_sum > 0:
|
||||
return complex(z_sq_sum / z.real, math.inf)
|
||||
elif z.real != 0 and z_sq_sum < 0:
|
||||
return complex(z_sq_sum / z.real, -math.inf)
|
||||
elif z.imag != 0 and z_sq_sum > 0:
|
||||
return complex(math.inf, z_sq_sum / z.real)
|
||||
elif z.imag != 0 and z_sq_sum < 0:
|
||||
return complex(-math.inf, z_sq_sum / z.real)
|
||||
elif z_sq_sum == 0:
|
||||
return complex(0, 0)
|
||||
else:
|
||||
if z.real == 0 and z.imag == 0:
|
||||
return complex(math.inf, math.inf)
|
||||
if z_sq_sum == 0:
|
||||
return complex(0, 0)
|
||||
if z.imag == 0:
|
||||
return complex(z_sq_sum / z.real, math.copysign(math.inf, z_sq_sum))
|
||||
if z.real == 0:
|
||||
return complex(math.copysign(math.inf, z_sq_sum), z_sq_sum / z.real)
|
||||
return complex(z_sq_sum / z.real, z_sq_sum / z.imag)
|
||||
|
||||
|
||||
def impedance_to_capacitance(z: complex, freq: float) -> float:
|
||||
|
@ -87,7 +82,10 @@ def reflection_coefficient(z: complex, ref_impedance: float = 50) -> complex:
|
|||
|
||||
def gamma_to_impedance(gamma: complex, ref_impedance: float = 50) -> complex:
|
||||
"""Calculate impedance from gamma"""
|
||||
return ((-gamma - 1) / (gamma - 1)) * ref_impedance
|
||||
try:
|
||||
return ((-gamma - 1) / (gamma - 1)) * ref_impedance
|
||||
except ZeroDivisionError:
|
||||
return math.inf
|
||||
|
||||
|
||||
def parseFrequency(freq: str) -> int:
|
||||
|
@ -117,7 +115,7 @@ class Datapoint(NamedTuple):
|
|||
mag = abs(self.z)
|
||||
if mag > 0:
|
||||
return 20 * math.log10(mag)
|
||||
return 0
|
||||
return -math.inf
|
||||
|
||||
@property
|
||||
def vswr(self) -> float:
|
||||
|
@ -174,4 +172,4 @@ class RFTools:
|
|||
|
||||
@staticmethod
|
||||
def parseFrequency(freq: str) -> int:
|
||||
return parseFrequency(freq)
|
||||
return parseFrequency(freq)
|
||||
|
|
|
@ -59,7 +59,7 @@ class Value:
|
|||
value: Union[Number, str] = 0,
|
||||
unit: str = "",
|
||||
fmt=Format()):
|
||||
assert 3 <= fmt.max_nr_digits <= 30
|
||||
assert 1 <= fmt.max_nr_digits <= 30
|
||||
assert -8 <= fmt.min_offset <= fmt.max_offset <= 8
|
||||
assert fmt.parse_clamp_min < fmt.parse_clamp_max
|
||||
assert fmt.printable_min < fmt.printable_max
|
||||
|
@ -91,7 +91,7 @@ class Value:
|
|||
|
||||
real = float(self._value) / (10 ** (offset * 3))
|
||||
|
||||
if fmt.max_nr_digits < 4:
|
||||
if fmt.max_nr_digits < 3:
|
||||
formstr = ".0f"
|
||||
else:
|
||||
max_digits = fmt.max_nr_digits + (
|
||||
|
|
|
@ -96,7 +96,7 @@ class SweepWorker(QtCore.QRunnable):
|
|||
return
|
||||
|
||||
span = sweep_to - sweep_from
|
||||
stepsize = int(span / (100 + (self.noSweeps-1)*101))
|
||||
stepsize = int(span / (self.vna.datapoints-1 + (self.noSweeps-1)*self.vna.datapoints))
|
||||
|
||||
# Setup complete
|
||||
|
||||
|
@ -110,14 +110,14 @@ class SweepWorker(QtCore.QRunnable):
|
|||
if self.stopped:
|
||||
logger.debug("Stopping sweeping as signalled")
|
||||
break
|
||||
start = sweep_from + i * 101 * stepsize
|
||||
freq, val11, val21 = self.readAveragedSegment(start, start + 100 * stepsize, self.averages)
|
||||
start = sweep_from + i * self.vna.datapoints * stepsize
|
||||
freq, val11, val21 = self.readAveragedSegment(start, start + (self.vna.datapoints-1) * stepsize, self.averages)
|
||||
|
||||
frequencies += freq
|
||||
values += val11
|
||||
values21 += val21
|
||||
|
||||
self.percentage = (i + 1) * 100 / self.noSweeps
|
||||
self.percentage = (i + 1) * (self.vna.datapoints-1) / self.noSweeps
|
||||
logger.debug("Saving acquired data")
|
||||
self.saveData(frequencies, values, values21)
|
||||
|
||||
|
@ -127,9 +127,9 @@ class SweepWorker(QtCore.QRunnable):
|
|||
if self.stopped:
|
||||
logger.debug("Stopping sweeping as signalled")
|
||||
break
|
||||
start = sweep_from + i*101*stepsize
|
||||
start = sweep_from + i*self.vna.datapoints*stepsize
|
||||
try:
|
||||
freq, val11, val21 = self.readSegment(start, start+100*stepsize)
|
||||
freq, val11, val21 = self.readSegment(start, start+(self.vna.datapoints-1)*stepsize)
|
||||
|
||||
frequencies += freq
|
||||
values += val11
|
||||
|
@ -156,11 +156,11 @@ class SweepWorker(QtCore.QRunnable):
|
|||
if self.stopped:
|
||||
logger.debug("Stopping sweeping as signalled")
|
||||
break
|
||||
start = sweep_from + i * 101 * stepsize
|
||||
start = sweep_from + i * self.vna.datapoints * stepsize
|
||||
try:
|
||||
_, values, values21 = self.readSegment(start, start + 100 * stepsize)
|
||||
_, values, values21 = self.readSegment(start, start + (self.vna.datapoints-1) * stepsize)
|
||||
logger.debug("Updating acquired data")
|
||||
self.updateData(values, values21, i)
|
||||
self.updateData(values, values21, i, self.vna.datapoints)
|
||||
except NanoVNAValueException as e:
|
||||
self.error_message = str(e)
|
||||
self.stopped = True
|
||||
|
@ -196,8 +196,8 @@ class SweepWorker(QtCore.QRunnable):
|
|||
raw_data21 = Datapoint(freq, re21, im21)
|
||||
data11, data21 = self.applyCalibration([raw_data11], [raw_data21])
|
||||
|
||||
self.data11[offset * segment_size + i] = data11
|
||||
self.data21[offset * segment_size + i] = data21
|
||||
self.data11[offset * segment_size + i] = data11[0]
|
||||
self.data21[offset * segment_size + i] = data21[0]
|
||||
self.rawData11[offset * segment_size + i] = raw_data11
|
||||
self.rawData21[offset * segment_size + i] = raw_data21
|
||||
logger.debug("Saving data to application (%d and %d points)", len(self.data11), len(self.data21))
|
||||
|
|
|
@ -139,7 +139,7 @@ class Touchstone:
|
|||
z = cmath.rect(float(v), math.radians(float(next(vals))))
|
||||
next(data_list).append(Datapoint(freq, z.real, z.imag))
|
||||
if self.opts.format == "db":
|
||||
z = cmath.rect(math.exp(float(v) / 20), math.radians(float(next(vals))))
|
||||
z = cmath.rect(10 ** (float(v) / 20), math.radians(float(next(vals))))
|
||||
next(data_list).append(Datapoint(freq, z.real, z.imag))
|
||||
|
||||
def load(self):
|
||||
|
|
|
@ -14,5 +14,5 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
version = '0.2.2'
|
||||
version = '0.2.3'
|
||||
debug = False
|
||||
|
|
Plik diff jest za duży
Load Diff
Plik diff jest za duży
Load Diff
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,2 @@
|
|||
# MHz S RI R 50
|
||||
28.000000 0.0 0.0
|
|
@ -0,0 +1,11 @@
|
|||
# HZ S DB R 50
|
||||
1.465003750937734E9 -1.396696934098445E1 3.349568174026075 -2.261174260421613 1.620714227806657E2 -2.255142489055356 1.626927267495817E2 -1.442619251233750E1 4.509019031432469
|
||||
1.465503875968992E9 -1.369876098541207E1 3.479938266374748 -2.224036324693064 1.631219183133622E2 -2.230062353322664 1.612120965710581E2 -1.353367611139944E1 1.954291818822123
|
||||
1.466004001000250E9 -1.379085768177502E1 6.788018243134909 -2.234675921557394 1.616283462983217E2 -2.241155297558765 1.629287928899836E2 -1.404466816442995E1 2.475833229081635
|
||||
1.466504126031508E9 -1.424627608260705E1 4.706658182189423 -2.267732265261066 1.630281926898599E2 -2.279113484903308 1.637911206096939E2 -1.403985522782244E1 6.258852806593705
|
||||
1.467004251062766E9 -1.388823218559945E1 3.744481757023677 -2.243118570936963 1.619407156405345E2 -2.256689625298824 1.620401320385134E2 -1.437657711076861E1 4.378386297027917
|
||||
1.467504376094024E9 -1.363131206363558E1 4.481745062167305 -2.234202921073654 1.609017179760186E2 -2.231499875527908 1.621150648113463E2 -1.403567006946793E1 6.008756896907650
|
||||
1.468004501125281E9 -1.357308913865166E1 6.752417012204787 -2.190762960848804 1.592255394852820E2 -2.213893899730369 1.604919620912775E2 -1.392469613271226E1 5.802146694755130
|
||||
1.468504626156539E9 -1.328017288789247E1 4.347321859152585 -2.211094613088734 1.590669733070027E2 -2.207418277887152 1.574936196343803E2 -1.424893658605293E1 2.965770345166085
|
||||
1.469004751187797E9 -1.308760188829710E1 1.035833400693584E1 -2.213146425167909 1.605810413752405E2 -2.241527794979899 1.595688676701842E2 -1.413210094282936E1 7.337633110667883
|
||||
1.469504876219055E9 -1.441098551455218E1 9.046042843197457 -2.219676590923017 1.602570163112450E2 -2.255210742654332 1.603381378830466E2 -1.369031345080353E1 7.167051366612957
|
|
@ -0,0 +1,11 @@
|
|||
# MHZ S MA R 50
|
||||
10.0000 0.915 -63.193 57.033 142.886 0.013 51.862 0.525 -89.730
|
||||
20.0000 0.830 -99.758 40.867 122.518 0.021 32.533 0.638 -123.891
|
||||
30.0000 0.787 -121.018 30.176 110.861 0.022 20.814 0.684 -140.388
|
||||
40.0000 0.765 -134.056 23.331 103.406 0.023 15.181 0.708 -149.561
|
||||
50.0000 0.757 -141.913 19.148 98.789 0.023 10.360 0.719 -154.726
|
||||
60.0000 0.752 -147.447 16.060 94.882 0.024 5.949 0.724 -158.168
|
||||
70.0000 0.751 -151.523 13.785 91.746 0.024 2.789 0.732 -160.919
|
||||
80.0000 0.750 -154.603 12.098 88.870 0.024 2.787 0.735 -162.634
|
||||
90.0000 0.751 -156.729 10.716 86.488 0.024 -0.815 0.740 -164.178
|
||||
100.000 0.750 -158.996 9.593 84.260 0.024 -3.226 0.742 -165.291
|
|
@ -0,0 +1,112 @@
|
|||
# NanoVNASaver
|
||||
# A python program to view and export Touchstone data from a NanoVNA
|
||||
# Copyright (C) 2019. Rune B. Broberg
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import unittest
|
||||
|
||||
# Import targets to be tested
|
||||
from NanoVNASaver import Formatting as fmt
|
||||
|
||||
|
||||
class TestCases(unittest.TestCase):
|
||||
|
||||
def test_format_frequency(self):
|
||||
self.assertEqual(fmt.format_frequency(1), '1.00000 Hz')
|
||||
self.assertEqual(fmt.format_frequency(12), '12.0000 Hz')
|
||||
self.assertEqual(fmt.format_frequency(123), '123.000 Hz')
|
||||
self.assertEqual(fmt.format_frequency(1234), '1.23400 kHz')
|
||||
self.assertEqual(fmt.format_frequency(1234567), '1.23457 MHz')
|
||||
self.assertEqual(fmt.format_frequency(1234567890), '1.23457 GHz')
|
||||
self.assertEqual(fmt.format_frequency(0), '0.00000 Hz')
|
||||
self.assertEqual(fmt.format_frequency(-1), '-1.00000 Hz')
|
||||
|
||||
def test_format_frequency_inputs(self):
|
||||
self.assertEqual(fmt.format_frequency_inputs(1), '1Hz')
|
||||
self.assertEqual(fmt.format_frequency_inputs(12), '12Hz')
|
||||
self.assertEqual(fmt.format_frequency_inputs(123), '123Hz')
|
||||
self.assertEqual(fmt.format_frequency_inputs(1234), '1.234kHz')
|
||||
self.assertEqual(fmt.format_frequency_inputs(1234567), '1.234567MHz')
|
||||
self.assertEqual(fmt.format_frequency_inputs(1234567890), '1.23456789GHz')
|
||||
self.assertEqual(fmt.format_frequency_inputs(0), '0Hz')
|
||||
self.assertEqual(fmt.format_frequency_inputs(-1), '- Hz')
|
||||
|
||||
def test_format_gain(self):
|
||||
self.assertEqual(fmt.format_gain(1), '1.000 dB')
|
||||
self.assertEqual(fmt.format_gain(12), '12.000 dB')
|
||||
self.assertEqual(fmt.format_gain(1.23456), '1.235 dB')
|
||||
self.assertEqual(fmt.format_gain(-1), '-1.000 dB')
|
||||
self.assertEqual(fmt.format_gain(-1, invert=True), '1.000 dB')
|
||||
|
||||
def test_format_q_factor(self):
|
||||
self.assertEqual(fmt.format_q_factor(1), '1')
|
||||
self.assertEqual(fmt.format_q_factor(12), '12')
|
||||
self.assertEqual(fmt.format_q_factor(123), '123')
|
||||
self.assertEqual(fmt.format_q_factor(1234), '1234')
|
||||
self.assertEqual(fmt.format_q_factor(12345), '\N{INFINITY}')
|
||||
self.assertEqual(fmt.format_q_factor(-1), '\N{INFINITY}')
|
||||
self.assertEqual(fmt.format_q_factor(1.2345), '1.234')
|
||||
|
||||
def test_format_vswr(self):
|
||||
self.assertEqual(fmt.format_vswr(1), '1.000')
|
||||
self.assertEqual(fmt.format_vswr(1.234), '1.234')
|
||||
self.assertEqual(fmt.format_vswr(12345.12345), '12345.123')
|
||||
|
||||
def test_format_resistance(self):
|
||||
self.assertEqual(fmt.format_resistance(1), '1 \N{OHM SIGN}')
|
||||
self.assertEqual(fmt.format_resistance(12), '12 \N{OHM SIGN}')
|
||||
self.assertEqual(fmt.format_resistance(123), '123 \N{OHM SIGN}')
|
||||
self.assertEqual(fmt.format_resistance(1234), '1.234 k\N{OHM SIGN}')
|
||||
self.assertEqual(fmt.format_resistance(12345), '12.345 k\N{OHM SIGN}')
|
||||
self.assertEqual(fmt.format_resistance(123456), '123.46 k\N{OHM SIGN}')
|
||||
self.assertEqual(fmt.format_resistance(-1), '- \N{OHM SIGN}')
|
||||
|
||||
def test_format_capacitance(self):
|
||||
self.assertEqual(fmt.format_capacitance(1), '1 F')
|
||||
self.assertEqual(fmt.format_capacitance(1e-3), '1 mF')
|
||||
self.assertEqual(fmt.format_capacitance(1e-6), '1 µF')
|
||||
self.assertEqual(fmt.format_capacitance(1e-9), '1 nF')
|
||||
self.assertEqual(fmt.format_capacitance(1e-12), '1 pF')
|
||||
self.assertEqual(fmt.format_capacitance(-1), '-1 F')
|
||||
self.assertEqual(fmt.format_capacitance(-1, False), '- pF')
|
||||
|
||||
def test_format_inductance(self):
|
||||
self.assertEqual(fmt.format_inductance(1), '1 H')
|
||||
self.assertEqual(fmt.format_inductance(1e-3), '1 mH')
|
||||
self.assertEqual(fmt.format_inductance(1e-6), '1 µH')
|
||||
self.assertEqual(fmt.format_inductance(1e-9), '1 nH')
|
||||
self.assertEqual(fmt.format_inductance(1e-12), '1 pH')
|
||||
self.assertEqual(fmt.format_inductance(-1), '-1 H')
|
||||
self.assertEqual(fmt.format_inductance(-1, False), '- nH')
|
||||
|
||||
def test_format_group_delay(self):
|
||||
self.assertEqual(fmt.format_group_delay(1), '1.0000 s')
|
||||
self.assertEqual(fmt.format_group_delay(1e-9), '1.0000 ns')
|
||||
self.assertEqual(fmt.format_group_delay(1.23456e-9), '1.2346 ns')
|
||||
|
||||
def test_format_phase(self):
|
||||
self.assertEqual(fmt.format_phase(0), '0.00°')
|
||||
self.assertEqual(fmt.format_phase(1), '57.30°')
|
||||
self.assertEqual(fmt.format_phase(-1), '-57.30°')
|
||||
self.assertEqual(fmt.format_phase(3.1416), '180.00°')
|
||||
self.assertEqual(fmt.format_phase(6.2831), '360.00°')
|
||||
self.assertEqual(fmt.format_phase(9.4247), '540.00°')
|
||||
self.assertEqual(fmt.format_phase(-3.1416), '-180.00°')
|
||||
|
||||
def test_format_complex_imp(self):
|
||||
self.assertEqual(fmt.format_complex_imp(complex(1, 0)), '1+j0 \N{OHM SIGN}')
|
||||
self.assertEqual(fmt.format_complex_imp(complex(1234, 1234)), '1.23k+j1.23k \N{OHM SIGN}')
|
||||
self.assertEqual(fmt.format_complex_imp(complex(1234, -1234)), '1.23k-j1.23k \N{OHM SIGN}')
|
||||
self.assertEqual(fmt.format_complex_imp(complex(1.234, 1234)), '1.23+j1.23k \N{OHM SIGN}')
|
||||
self.assertEqual(fmt.format_complex_imp(complex(-1, 1.23e-3)), '- +j1.23m \N{OHM SIGN}')
|
|
@ -21,7 +21,8 @@ from NanoVNASaver.RFTools import Datapoint, \
|
|||
norm_to_impedance, impedance_to_norm, \
|
||||
reflection_coefficient, gamma_to_impedance, clamp_value, \
|
||||
parallel_to_serial, serial_to_parallel, \
|
||||
impedance_to_capacitance, impedance_to_inductance
|
||||
impedance_to_capacitance, impedance_to_inductance, \
|
||||
groupDelay
|
||||
import math
|
||||
|
||||
|
||||
|
@ -63,6 +64,7 @@ class TestRFTools(unittest.TestCase):
|
|||
def test_gamma_to_impedance(self):
|
||||
self.assertEqual(gamma_to_impedance(0), 50)
|
||||
self.assertAlmostEqual(gamma_to_impedance(0.2), 75)
|
||||
self.assertEqual(gamma_to_impedance(1), math.inf)
|
||||
# TODO: insert more test values here
|
||||
|
||||
def test_clamp_value(self):
|
||||
|
@ -94,6 +96,20 @@ class TestRFTools(unittest.TestCase):
|
|||
impedance_to_inductance(complex(50, 159.1549), 100000),
|
||||
2.533e-4)
|
||||
|
||||
def test_groupDelay(self):
|
||||
dpoints = [
|
||||
Datapoint(100000, 0.1091, 0.3118),
|
||||
Datapoint(100001, 0.1091, 0.3124),
|
||||
Datapoint(100002, 0.1091, 0.3130),
|
||||
]
|
||||
dpoints0 = [
|
||||
Datapoint(100000, 0.1091, 0.3118),
|
||||
Datapoint(100000, 0.1091, 0.3124),
|
||||
Datapoint(100000, 0.1091, 0.3130),
|
||||
]
|
||||
self.assertAlmostEqual(groupDelay(dpoints, 1), -9.514e-5)
|
||||
self.assertEqual(groupDelay(dpoints0, 1), 0.0)
|
||||
|
||||
|
||||
class TestRFToolsDatapoint(unittest.TestCase):
|
||||
|
||||
|
@ -101,11 +117,12 @@ class TestRFToolsDatapoint(unittest.TestCase):
|
|||
self.dp = Datapoint(100000, 0.1091, 0.3118)
|
||||
self.dp0 = Datapoint(100000, 0, 0)
|
||||
self.dp50 = Datapoint(100000, 1, 0)
|
||||
self.dp75 = Datapoint(100000, 0.2, 0)
|
||||
|
||||
def test_properties(self):
|
||||
self.assertEqual(self.dp.z, complex(0.1091, 0.3118))
|
||||
self.assertAlmostEqual(self.dp.phase, 1.23420722)
|
||||
self.assertEqual(self.dp0.gain, 0.0)
|
||||
self.assertEqual(self.dp0.gain, -math.inf)
|
||||
self.assertAlmostEqual(self.dp.gain, -9.6208748)
|
||||
self.assertEqual(self.dp50.vswr, 1.0)
|
||||
self.assertAlmostEqual(self.dp.vswr, 1.9865736)
|
||||
|
@ -114,4 +131,7 @@ class TestRFToolsDatapoint(unittest.TestCase):
|
|||
self.assertAlmostEqual(self.dp.impedance(75),
|
||||
complex(74.99628755, 52.49617517))
|
||||
self.assertEqual(self.dp0.qFactor(), 0.0)
|
||||
self.assertEqual(self.dp75.qFactor(), 0.0)
|
||||
self.assertAlmostEqual(self.dp.qFactor(), 0.6999837)
|
||||
self.assertAlmostEqual(self.dp.capacitiveEquivalent(), -4.54761539e-08)
|
||||
self.assertAlmostEqual(self.dp.inductiveEquivalent(), 5.57001e-05)
|
||||
|
|
|
@ -23,13 +23,15 @@ from math import inf
|
|||
|
||||
F_DEFAULT = Format()
|
||||
|
||||
F_ASSERT_DIGITS_1 = Format(max_nr_digits=2)
|
||||
F_ASSERT_DIGITS_0 = Format(max_nr_digits=0)
|
||||
F_ASSERT_DIGITS_2 = Format(max_nr_digits=31)
|
||||
F_ASSERT_OFFSET_1 = Format(min_offset=-9)
|
||||
F_ASSERT_OFFSET_2 = Format(max_offset=9)
|
||||
F_ASSERT_OFFSET_3 = Format(min_offset=9)
|
||||
F_ASSERT_CLAMP = Format(parse_clamp_min=10, parse_clamp_max=9)
|
||||
|
||||
F_DIGITS_1 = Format(max_nr_digits=1, min_offset=-2,
|
||||
max_offset=2, assume_infinity=False)
|
||||
F_DIGITS_3 = Format(max_nr_digits=3, min_offset=-2,
|
||||
max_offset=2, assume_infinity=False)
|
||||
F_DIGITS_4 = Format(max_nr_digits=4)
|
||||
|
@ -42,7 +44,7 @@ F_WITH_UNDERSCORE = Format(space_str="_")
|
|||
class TestTSIToolsValue(unittest.TestCase):
|
||||
|
||||
def test_format_assertions(self):
|
||||
self.assertRaises(AssertionError, Value, fmt=F_ASSERT_DIGITS_1)
|
||||
self.assertRaises(AssertionError, Value, fmt=F_ASSERT_DIGITS_0)
|
||||
self.assertRaises(AssertionError, Value, fmt=F_ASSERT_DIGITS_2)
|
||||
self.assertRaises(AssertionError, Value, fmt=F_ASSERT_OFFSET_1)
|
||||
self.assertRaises(AssertionError, Value, fmt=F_ASSERT_OFFSET_2)
|
||||
|
@ -95,8 +97,8 @@ class TestTSIToolsValue(unittest.TestCase):
|
|||
self.assertEqual(str(Value(1e-30)), "0.00000")
|
||||
self.assertEqual(float(Value(1e-30)), 1e-30)
|
||||
|
||||
def test_format_digits_3(self):
|
||||
v = Value(fmt=F_DIGITS_3)
|
||||
def test_format_digits_1(self):
|
||||
v = Value(fmt=F_DIGITS_1)
|
||||
self.assertEqual(str(v.parse("1")), "1")
|
||||
self.assertEqual(str(v.parse("10")), "10")
|
||||
self.assertEqual(str(v.parse("100")), "100")
|
||||
|
@ -109,6 +111,20 @@ class TestTSIToolsValue(unittest.TestCase):
|
|||
self.assertEqual(str(v.parse("1e-3")), "1m")
|
||||
self.assertEqual(str(v.parse("1e-9")), "0")
|
||||
|
||||
def test_format_digits_3(self):
|
||||
v = Value(fmt=F_DIGITS_3)
|
||||
self.assertEqual(str(v.parse("1")), "1.00")
|
||||
self.assertEqual(str(v.parse("10")), "10.0")
|
||||
self.assertEqual(str(v.parse("100")), "100")
|
||||
self.assertEqual(str(v.parse("1e3")), "1.00k")
|
||||
self.assertEqual(str(v.parse("1e4")), "10.0k")
|
||||
self.assertEqual(str(v.parse("1e5")), "100k")
|
||||
self.assertEqual(str(v.parse("1e9")), "1000M")
|
||||
self.assertEqual(str(v.parse("1e-1")), "100m")
|
||||
self.assertEqual(str(v.parse("1e-2")), "10.0m")
|
||||
self.assertEqual(str(v.parse("1e-3")), "1.00m")
|
||||
self.assertEqual(str(v.parse("1e-9")), "0.00")
|
||||
|
||||
def test_format_digits_4(self):
|
||||
v = Value(fmt=F_DIGITS_4)
|
||||
self.assertEqual(str(v.parse("1")), "1.000")
|
||||
|
|
|
@ -73,6 +73,29 @@ class TestTouchstoneTouchstone(unittest.TestCase):
|
|||
self.assertEqual(len(ts.s22data), 1020)
|
||||
self.assertIn("! Vector Network Analyzer VNA R2", ts.comments)
|
||||
|
||||
ts = Touchstone("./test/data/ma.s2p")
|
||||
ts.load()
|
||||
self.assertEqual(str(ts.opts), "# MHZ S MA R 50")
|
||||
|
||||
ts = Touchstone("./test/data/db.s2p")
|
||||
ts.load()
|
||||
self.assertEqual(str(ts.opts), "# HZ S DB R 50")
|
||||
|
||||
def test_db_conversation(self):
|
||||
ts_db = Touchstone("./test/data/attenuator-0643_DB.s2p")
|
||||
ts_db.load()
|
||||
ts_ri = Touchstone("./test/data/attenuator-0643_RI.s2p")
|
||||
ts_ri.load()
|
||||
ts_ma = Touchstone("./test/data/attenuator-0643_MA.s2p")
|
||||
ts_ma.load()
|
||||
self.assertEqual(len(ts_db.s11data), len(ts_ri.s11data))
|
||||
for dps_db, dps_ri in zip(ts_db.s11data, ts_ri.s11data):
|
||||
self.assertAlmostEqual(dps_db.z, dps_ri.z, places=5)
|
||||
|
||||
self.assertEqual(len(ts_db.s11data), len(ts_ma.s11data))
|
||||
for dps_db, dps_ma in zip(ts_db.s11data, ts_ma.s11data):
|
||||
self.assertAlmostEqual(dps_db.z, dps_ma.z, places=5)
|
||||
|
||||
def test_load_scikit(self):
|
||||
ts = Touchstone("./test/data/scikit_unordered.s2p")
|
||||
with self.assertLogs(level=logging.WARNING) as cm:
|
||||
|
|
|
@ -31,7 +31,7 @@ import unittest
|
|||
if __name__ == '__main__':
|
||||
sys.path.append('.')
|
||||
loader = unittest.TestLoader()
|
||||
tests = loader.discover('.')
|
||||
tests = loader.discover('./test')
|
||||
testRunner = unittest.runner.TextTestRunner(
|
||||
failfast=False,
|
||||
verbosity=2)
|
||||
|
|
Ładowanie…
Reference in New Issue