Merge branch 'upstream_Development'

pull/106/head
Holger Mueller 2019-11-16 14:59:14 +01:00
commit ef185ae219
3 zmienionych plików z 79 dodań i 92 usunięć

Wyświetl plik

@ -62,6 +62,8 @@ class Chart(QtWidgets.QWidget):
draggedBox = False draggedBox = False
draggedBoxStart = (0, 0) draggedBoxStart = (0, 0)
draggedBoxCurrent = (-1, -1) draggedBoxCurrent = (-1, -1)
moveStartX = -1
moveStartY = -1
isPopout = False isPopout = False
popoutRequested = pyqtSignal(object) popoutRequested = pyqtSignal(object)
@ -204,12 +206,18 @@ class Chart(QtWidgets.QWidget):
if event.buttons() == QtCore.Qt.RightButton: if event.buttons() == QtCore.Qt.RightButton:
event.ignore() event.ignore()
return return
elif event.buttons() == QtCore.Qt.MiddleButton:
# Drag event
event.accept()
self.moveStartX = event.x()
self.moveStartY = event.y()
return
if event.modifiers() == QtCore.Qt.ShiftModifier: if event.modifiers() == QtCore.Qt.ShiftModifier:
self.draggedMarker = self.getNearestMarker(event.x(), event.y()) self.draggedMarker = self.getNearestMarker(event.x(), event.y())
elif event.modifiers() == QtCore.Qt.ControlModifier: elif event.modifiers() == QtCore.Qt.ControlModifier:
event.accept()
self.draggedBox = True self.draggedBox = True
self.draggedBoxStart = (event.x(), event.y()) self.draggedBoxStart = (event.x(), event.y())
event.accept()
return return
self.mouseMoveEvent(event) self.mouseMoveEvent(event)
@ -593,6 +601,18 @@ class FrequencyChart(Chart):
if a0.buttons() == QtCore.Qt.RightButton: if a0.buttons() == QtCore.Qt.RightButton:
a0.ignore() a0.ignore()
return return
if a0.buttons() == QtCore.Qt.MiddleButton:
# Drag the display
a0.accept()
if self.moveStartX != -1 and self.moveStartY != -1:
dx = self.moveStartX - a0.x()
dy = self.moveStartY - a0.y()
self.zoomTo(self.leftMargin + dx, self.topMargin + dy,
self.leftMargin + self.chartWidth + dx, self.topMargin + self.chartHeight + dy)
self.moveStartX = a0.x()
self.moveStartY = a0.y()
return
if a0.modifiers() == QtCore.Qt.ControlModifier: if a0.modifiers() == QtCore.Qt.ControlModifier:
# Dragging a box # Dragging a box
if not self.draggedBox: if not self.draggedBox:

Wyświetl plik

@ -15,6 +15,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import collections import collections
import math import math
from numbers import Number
from typing import List from typing import List
from NanoVNASaver.SITools import Value, Format from NanoVNASaver.SITools import Value, Format
@ -33,7 +34,7 @@ class RFTools:
return re50, im50 return re50, im50
@staticmethod @staticmethod
def gain(data: Datapoint): def gain(data: Datapoint) -> float:
# re50, im50 = normalize50(data) # re50, im50 = normalize50(data)
# Calculate the gain / reflection coefficient # Calculate the gain / reflection coefficient
# mag = math.sqrt((re50 - 50) * (re50 - 50) + im50 * im50) / \ # mag = math.sqrt((re50 - 50) * (re50 - 50) + im50 * im50) / \
@ -43,8 +44,7 @@ class RFTools:
mag = math.sqrt(data.re**2 + data.im**2) mag = math.sqrt(data.re**2 + data.im**2)
if mag > 0: if mag > 0:
return 20 * math.log10(mag) return 20 * math.log10(mag)
else: return 0
return 0
@staticmethod @staticmethod
def qualityFactor(data: Datapoint): def qualityFactor(data: Datapoint):
@ -73,7 +73,7 @@ class RFTools:
return "- pF" return "- pF"
capacitance = 1 / (freq * 2 * math.pi * im50) capacitance = 1 / (freq * 2 * math.pi * im50)
return str(Value(-capacitance, "F", Format(max_nr_digits=5, space_str=" "))) return str(Value(-capacitance, "F", Format(max_nr_digits=5, space_str=" ")))
@staticmethod @staticmethod
def inductanceEquivalent(im50, freq) -> str: def inductanceEquivalent(im50, freq) -> str:
if freq == 0: if freq == 0:
@ -82,85 +82,54 @@ class RFTools:
return str(Value(inductance, "H", Format(max_nr_digits=5, space_str=" "))) return str(Value(inductance, "H", Format(max_nr_digits=5, space_str=" ")))
@staticmethod @staticmethod
def formatFrequency(freq): def formatFrequency(freq: Number) -> str:
return str(Value(freq, "Hz", Format(max_nr_digits=6))) return str(Value(freq, "Hz"))
@staticmethod @staticmethod
def formatShortFrequency(freq): def formatShortFrequency(freq: Number) -> str:
return str(Value(freq, "Hz", Format(max_nr_digits=4))) return str(Value(freq, "Hz", Format(max_nr_digits=4)))
@staticmethod @staticmethod
def formatSweepFrequency(freq: int, def formatSweepFrequency(freq: Number) -> str:
mindigits: int = 2, return str(Value(freq, "Hz", Format(max_nr_digits=5)))
appendHz: bool = True,
insertSpace: bool = False,
countDot: bool = True,
assumeInfinity: bool = True) -> str:
""" Format frequency with SI prefixes
mindigits count refers to the number of decimal place digits
that will be shown, padded with zeroes if needed.
"""
freqstr = str(freq)
freqlen = len(freqstr)
# sanity checks
if freqlen > 15:
if assumeInfinity:
return "\N{INFINITY}"
raise ValueError("Frequency too big. More than 15 digits!")
if freq < 1:
return " - " + (" " if insertSpace else "") + ("Hz" if appendHz else "")
si_index = (freqlen - 1) // 3
dot_pos = freqlen % 3 or 3
intfstr = freqstr[:dot_pos]
decfstr = freqstr[dot_pos:]
nzdecfstr = decfstr.rstrip('0')
if si_index != 0:
while len(nzdecfstr) < mindigits:
nzdecfstr += '0'
freqstr = intfstr + ("." if len(nzdecfstr) > 0 else "") + nzdecfstr
return freqstr + (" " if insertSpace else "") + PREFIXES[si_index] + ("Hz" if appendHz else "")
@staticmethod @staticmethod
def parseFrequency(freq: str) -> int: def parseFrequency(freq: str) -> int:
parser = Value(0, "Hz") parser = Value(0, "Hz", Format(parse_sloppy_unit=True, parse_sloppy_kilo=True))
try: try:
return round(parser.parse(freq)) return round(parser.parse(freq))
except (ValueError, IndexError): except (ValueError, IndexError):
return -1 return -1
@staticmethod @staticmethod
def phaseAngle(data: Datapoint): def phaseAngle(data: Datapoint) -> float:
re = data.re re = data.re
im = data.im im = data.im
return math.degrees(math.atan2(im, re)) return math.degrees(math.atan2(im, re))
@staticmethod @staticmethod
def phaseAngleRadians(data: Datapoint): def phaseAngleRadians(data: Datapoint) -> float:
re = data.re re = data.re
im = data.im im = data.im
return math.atan2(im, re) return math.atan2(im, re)
@staticmethod
def clamp_int(value: int, min: int, max: int) -> int:
assert min <= max
if value < min:
return min
if value > max:
return max
return value
@staticmethod @staticmethod
def groupDelay(data: List[Datapoint], index: int) -> float: def groupDelay(data: List[Datapoint], index: int) -> float:
if index == 0: index0 = RFTools.clamp_int(index - 1, 0, len(data) - 1)
angle0 = RFTools.phaseAngleRadians(data[0]) index1 = RFTools.clamp_int(index + 1, 0, len(data) - 1)
angle1 = RFTools.phaseAngleRadians(data[1]) angle0 = RFTools.phaseAngleRadians(data[index0])
freq0 = data[0].freq angle1 = RFTools.phaseAngleRadians(data[index1])
freq1 = data[1].freq freq0 = data[index0].freq
elif index == len(data) - 1: freq1 = data[index1].freq
angle0 = RFTools.phaseAngleRadians(data[-2])
angle1 = RFTools.phaseAngleRadians(data[-1])
freq0 = data[-2].freq
freq1 = data[-1].freq
else:
angle0 = RFTools.phaseAngleRadians(data[index-1])
angle1 = RFTools.phaseAngleRadians(data[index+1])
freq0 = data[index-1].freq
freq1 = data[index+1].freq
delta_angle = (angle1 - angle0) delta_angle = (angle1 - angle0)
if abs(delta_angle) > math.tau: if abs(delta_angle) > math.tau:
if delta_angle > 0: if delta_angle > 0:

Wyświetl plik

@ -15,45 +15,35 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
import math import math
from typing import NamedTuple
from numbers import Number from numbers import Number
PREFIXES = ("y", "z", "a", "f", "p", "n", "µ", "m", PREFIXES = ("y", "z", "a", "f", "p", "n", "µ", "m",
"", "k", "M", "G", "T", "P", "E", "Z", "Y") "", "k", "M", "G", "T", "P", "E", "Z", "Y")
class Format(NamedTuple):
class Format(object): max_nr_digits: int = 6
def __init__(self, fix_decimals: bool = False
max_nr_digits: int = 6, space_str: str = ""
fix_decimals: bool = False, assume_infinity: bool = True
space_str: str = "", min_offset: int = -8
assume_infinity: bool = True, max_offset: int = 8
min_offset: int = -8, parse_sloppy_unit: bool = False
max_offset: int = 8): parse_sloppy_kilo: bool = False
assert(min_offset >= -8 and max_offset <= 8 and min_offset < max_offset)
self.max_nr_digits = max_nr_digits
self.fix_decimals = fix_decimals
self.space_str = space_str
self.assume_infinity = assume_infinity
self.min_offset = min_offset
self.max_offset = max_offset
def __repr__(self):
return (f"{self.__class__.__name__}("
f"{self.max_nr_digits}, {self.fix_decimals}, "
f"'{self.space_str}', {self.assume_infinity}, "
f"{self.min_offset}, {self.max_offset})")
class Value(object): class Value():
def __init__(self, value: Number = 0, unit: str = "", fmt=Format()): def __init__(self, value: Number = 0, unit: str = "", fmt=Format()):
assert 3 <= fmt.max_nr_digits <= 27
assert -8 <= fmt.min_offset <= fmt.max_offset <= 8
self.value = value self.value = value
self._unit = unit self._unit = unit
self.fmt = fmt self.fmt = fmt
def __repr__(self): def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.value}, '{self._unit}', {self.fmt})" return f"{self.__class__.__name__}({self.value}, '{self._unit}', {self.fmt})"
def __str__(self): def __str__(self) -> str:
fmt = self.fmt fmt = self.fmt
if fmt.assume_infinity and abs(self.value) >= 10 ** ((fmt.max_offset + 1) * 3): if fmt.assume_infinity and abs(self.value) >= 10 ** ((fmt.max_offset + 1) * 3):
return ("-" if self.value < 0 else "") + "\N{INFINITY}" + fmt.space_str + self._unit return ("-" if self.value < 0 else "") + "\N{INFINITY}" + fmt.space_str + self._unit
@ -85,22 +75,30 @@ class Value(object):
return result + fmt.space_str + PREFIXES[offset + 8] + self._unit return result + fmt.space_str + PREFIXES[offset + 8] + self._unit
def parse(self, value: str): def parse(self, value: str) -> float:
value = value.replace(" ", "") # Ignore spaces value = value.replace(" ", "") # Ignore spaces
if self._unit and value.endswith(self._unit) or value.lower().endswith(self._unit.lower()): # strip unit
if self._unit and (
value.endswith(self._unit) or
(self.fmt.parse_sloppy_unit and
value.lower().endswith(self._unit.lower()))): # strip unit
value = value[:-len(self._unit)] value = value[:-len(self._unit)]
factor = 1 factor = 1
if self.fmt.parse_sloppy_kilo and value[-1] == "K": # fix for e.g. KHz
value = value[:-1] + "k"
if value[-1] in PREFIXES: if value[-1] in PREFIXES:
factor = 10 ** ((PREFIXES.index(value[-1]) - 8) * 3) factor = 10 ** ((PREFIXES.index(value[-1]) - 8) * 3)
value = value[:-1] value = value[:-1]
elif value[-1] == 'K':
# Fix for the very common KHz if self.fmt.assume_infinity and value == "\N{INFINITY}":
factor = 10 ** ((PREFIXES.index(value[-1].lower()) - 8) * 3) self.value = math.inf
value = value[:-1] elif self.fmt.assume_infinity and value == "-\N{INFINITY}":
self.value = float(value) * factor self.value = -math.inf
else:
self.value = float(value) * factor
return self.value return self.value
@property @property
def unit(self): def unit(self) -> str:
return self._unit return self._unit