Skip to content

Commit

Permalink
Merge pull request #400 from NanoVNA-Saver/testing
Browse files Browse the repository at this point in the history
v0.3.9
  • Loading branch information
zarath authored Jun 20, 2021
2 parents 97c7c8f + 0c352ee commit 9d1ea35
Show file tree
Hide file tree
Showing 32 changed files with 1,015 additions and 199 deletions.
7 changes: 7 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Default for all text files
* text=auto whitespace=trailing-space,tab-in-indent,tabwidth=2
*.py text=auto whitespace=trailing-space,tab-in-indent,tabwidth=4

# Denote all files that are truly binary and should not be modified.
*.png binary
*.jpg binary
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
settings.json
.gitignore
.coverage
/nanovna-saver.exe.spec
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

v0.3.9
------

- TX Power on V2
- New analysis
- Magnitude Z Chart
- VSWR Chart improvements

v0.3.8
------

Expand Down
2 changes: 1 addition & 1 deletion NanoVNASaver/About.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# 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.3.8"
VERSION = "0.3.9"
VERSION_URL = (
"https://raw.githubusercontent.com/"
"NanoVNA-Saver/nanovna-saver/master/NanoVNASaver/About.py")
Expand Down
102 changes: 99 additions & 3 deletions NanoVNASaver/Analysis/Analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,109 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
import math

import numpy as np
from scipy.signal import argrelextrema
from PyQt5 import QtWidgets
from scipy import signal

logger = logging.getLogger(__name__)


class Analysis:
_widget = None

@classmethod
def find_crossing_zero(cls, data, threshold=0):
'''
Find values crossing zero
return list of tuples (before, crossing, after)
indicating the index of data list
crossing is where data == 0
or data nearest 0
at maximum 1 value == 0
data must not start or end with 0
:param cls:
:param data: list of values
:param threshold: unused, for future manage flipping around 0
'''
my_data = np.array(data)
zeroes = np.where(my_data == 0)[0]

if 0 in zeroes:
raise ValueError("Data must non start with 0")

if len(data) - 1 in zeroes:
raise ValueError("Data must non end with 0")
crossing = [(n - 1, n, n + 1) for n in zeroes]

for n in np.where((my_data[:-1] * my_data[1:]) < 0)[0]:
if abs(data[n]) <= abs(data[n + 1]):
crossing.append((n, n, n + 1))
else:
crossing.append((n, n + 1, n + 1))

return crossing

@classmethod
def find_minimums(cls, data, threshold):
'''
Find values above threshold
return list of tuples (start, lowest, end)
indicating the index of data list
:param cls:
:param data: list of values
:param threshold:
'''

minimums = []
min_start = -1
min_idx = -1

min_val = threshold
for i, d in enumerate(data):
if d < threshold and i < len(data) - 1:
if d < min_val:
min_val = d
min_idx = i
if min_start == -1:
min_start = i
elif min_start != -1:
# We are above the threshold, and were in a section that was
# below
minimums.append((min_start, min_idx, i - 1))
min_start = -1
min_idx = -1
min_val = threshold
return minimums

@classmethod
def find_maximums(cls, data, threshold=None):
'''
Find peacs
:param cls:
:param data: list of values
:param threshold:
'''
peaks, _ = signal.find_peaks(
data, width=2, distance=3, prominence=1)

# my_data = np.array(data)
# maximums = argrelextrema(my_data, np.greater)[0]
if threshold is None:
return peaks
else:
return [k for k in peaks if data[k] > threshold]

def __init__(self, app: QtWidgets.QWidget):
self.app = app

Expand All @@ -50,8 +144,10 @@ def calculateRolloff(self, location1, location2):
if frequency_factor < 1:
frequency_factor = 1 / frequency_factor
attenuation = abs(gain1 - gain2)
logger.debug("Measured points: %d Hz and %d Hz", frequency1, frequency2)
logger.debug("Measured points: %d Hz and %d Hz",
frequency1, frequency2)
logger.debug("%f dB over %f factor", attenuation, frequency_factor)
octave_attenuation = attenuation / (math.log10(frequency_factor) / math.log10(2))
octave_attenuation = attenuation / \
(math.log10(frequency_factor) / math.log10(2))
decade_attenuation = attenuation / math.log10(frequency_factor)
return octave_attenuation, decade_attenuation
82 changes: 56 additions & 26 deletions NanoVNASaver/Analysis/PeakSearchAnalysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
import numpy as np

from NanoVNASaver.Analysis import Analysis
from NanoVNASaver.Formatting import format_vswr
from NanoVNASaver.Formatting import format_gain
from NanoVNASaver.Formatting import format_resistance
from NanoVNASaver.Formatting import format_frequency_short


logger = logging.getLogger(__name__)
Expand All @@ -38,8 +42,8 @@ def __init__(self, app):
super().__init__(app)

self._widget = QtWidgets.QWidget()
outer_layout = QtWidgets.QFormLayout()
self._widget.setLayout(outer_layout)
self.layout = QtWidgets.QFormLayout()
self._widget.setLayout(self.layout)

self.rbtn_data_group = QtWidgets.QButtonGroup()
self.rbtn_data_vswr = QtWidgets.QRadioButton("VSWR")
Expand Down Expand Up @@ -70,40 +74,56 @@ def __init__(self, app):

self.checkbox_move_markers = QtWidgets.QCheckBox()

outer_layout.addRow(QtWidgets.QLabel("<b>Settings</b>"))
outer_layout.addRow("Data source", self.rbtn_data_vswr)
outer_layout.addRow("", self.rbtn_data_resistance)
outer_layout.addRow("", self.rbtn_data_reactance)
outer_layout.addRow("", self.rbtn_data_s21_gain)
outer_layout.addRow(PeakSearchAnalysis.QHLine())
outer_layout.addRow("Peak type", self.rbtn_peak_positive)
outer_layout.addRow("", self.rbtn_peak_negative)
self.layout.addRow(QtWidgets.QLabel("<b>Settings</b>"))
self.layout.addRow("Data source", self.rbtn_data_vswr)
self.layout.addRow("", self.rbtn_data_resistance)
self.layout.addRow("", self.rbtn_data_reactance)
self.layout.addRow("", self.rbtn_data_s21_gain)
self.layout.addRow(PeakSearchAnalysis.QHLine())
self.layout.addRow("Peak type", self.rbtn_peak_positive)
self.layout.addRow("", self.rbtn_peak_negative)
# outer_layout.addRow("", self.rbtn_peak_both)
outer_layout.addRow(PeakSearchAnalysis.QHLine())
outer_layout.addRow("Max number of peaks", self.input_number_of_peaks)
outer_layout.addRow("Move markers", self.checkbox_move_markers)
outer_layout.addRow(PeakSearchAnalysis.QHLine())

outer_layout.addRow(QtWidgets.QLabel("<b>Results</b>"))
self.layout.addRow(PeakSearchAnalysis.QHLine())
self.layout.addRow("Max number of peaks", self.input_number_of_peaks)
self.layout.addRow("Move markers", self.checkbox_move_markers)
self.layout.addRow(PeakSearchAnalysis.QHLine())
self.layout.addRow(QtWidgets.QLabel("<b>Results</b>"))
self.results_header = self.layout.rowCount()

def runAnalysis(self):
self.reset()
data = []
sign = 1
count = self.input_number_of_peaks.value()
if self.rbtn_data_vswr.isChecked():
data = []
fn = format_vswr
for d in self.app.data11:
data11.append(d.vswr)
data.append(d.vswr)
elif self.rbtn_data_s21_gain.isChecked():
data = []
fn = format_gain
for d in self.app.data21:
data.append(d.gain)
elif self.rbtn_data_resistance.isChecked():
fn = format_resistance
for d in self.app.data11:
data.append(d.impedance().real)
elif self.rbtn_data_reactance.isChecked():
fn = str
for d in self.app.data11:
data.append(d.impedance().imag)

else:
logger.warning("Searching for peaks on unknown data")
return

if self.rbtn_peak_positive.isChecked():
peaks, _ = signal.find_peaks(data, width=3, distance=3, prominence=1)
peaks, _ = signal.find_peaks(
data, width=3, distance=3, prominence=1)
elif self.rbtn_peak_negative.isChecked():
peaks, _ = signal.find_peaks(np.array(data)*-1, width=3, distance=3, prominence=1)
sign = -1
data = [x * sign for x in data]
peaks, _ = signal.find_peaks(
data, width=3, distance=3, prominence=1)
# elif self.rbtn_peak_both.isChecked():
# peaks_max, _ = signal.find_peaks(data, width=3, distance=3, prominence=1)
# peaks_min, _ = signal.find_peaks(np.array(data)*-1, width=3, distance=3, prominence=1)
Expand All @@ -117,8 +137,8 @@ def runAnalysis(self):

# Having found the peaks, get the prominence data

for p in peaks:
logger.debug("Peak at %d", p)
for i, p in np.ndenumerate(peaks):
logger.debug("Peak %i at %d", i, p)
prominences = signal.peak_prominences(data, peaks)[0]
logger.debug("%d prominences", len(prominences))

Expand All @@ -131,9 +151,13 @@ def runAnalysis(self):
logger.debug("Prominence %f", prominences[i])
logger.debug("Index in sweep %d", peaks[i])
logger.debug("Frequency %d", self.app.data11[peaks[i]].freq)
logger.debug("Value %f", data[peaks[i]])
logger.debug("Value %f", sign * data[peaks[i]])
self.layout.addRow(
f"Freq {format_frequency_short(self.app.data11[peaks[i]].freq)}",
QtWidgets.QLabel(f" value {fn(sign * data[peaks[i]])}"
))

if self.checkbox_move_markers:
if self.checkbox_move_markers.isChecked():
if count > len(self.app.markers):
logger.warning("More peaks found than there are markers")
for i in range(min(count, len(self.app.markers))):
Expand All @@ -152,4 +176,10 @@ def runAnalysis(self):
logger.debug("Max peak at %d, value %f", max_idx, max_val)

def reset(self):
pass
logger.debug("Reset analysis")

logger.debug("Results start at %d, out of %d",
self.results_header, self.layout.rowCount())
for i in range(self.results_header, self.layout.rowCount()):
logger.debug("deleting %s", self.layout.rowCount())
self.layout.removeRow(self.layout.rowCount() - 1)
Loading

0 comments on commit 9d1ea35

Please sign in to comment.