Merge pull request #357 from gaionim/features/crossingZero

Additinal analysis methods added / better peak search algorythm
pull/368/head
Holger Müller 2020-12-31 14:04:56 +01:00 zatwierdzone przez GitHub
commit 90e4dc9332
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
5 zmienionych plików z 545 dodań i 62 usunięć

Wyświetl plik

@ -18,8 +18,10 @@
# 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__)
@ -27,6 +29,98 @@ 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
@ -50,8 +144,10 @@ class Analysis:
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

Wyświetl plik

@ -23,6 +23,10 @@ from scipy import signal
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__)
@ -38,8 +42,8 @@ class PeakSearchAnalysis(Analysis):
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")
@ -70,40 +74,56 @@ class PeakSearchAnalysis(Analysis):
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)
@ -117,8 +137,8 @@ class PeakSearchAnalysis(Analysis):
# 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))
@ -131,9 +151,13 @@ class PeakSearchAnalysis(Analysis):
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))):
@ -152,4 +176,10 @@ class PeakSearchAnalysis(Analysis):
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)

Wyświetl plik

@ -23,14 +23,32 @@ import numpy as np
from NanoVNASaver.Analysis import Analysis, PeakSearchAnalysis
from NanoVNASaver.Formatting import format_frequency
from NanoVNASaver.Formatting import format_complex_imp
from NanoVNASaver.RFTools import reflection_coefficient
import os
import csv
from NanoVNASaver.Marker.Values import Label
from NanoVNASaver.Marker.Widget import MarkerLabel
from NanoVNASaver.Marker.Widget import Marker
from collections import OrderedDict
from NanoVNASaver.Formatting import format_frequency_short
from NanoVNASaver.Formatting import format_resistance
logger = logging.getLogger(__name__)
def round_2(x):
return round(x, 2)
def format_resistence_neg(x):
return format_resistance(x, allow_negative=True)
class VSWRAnalysis(Analysis):
max_dips_shown = 3
vswr_limit_value = 1.5
class QHLine(QtWidgets.QFrame):
def __init__(self):
super().__init__()
@ -61,6 +79,7 @@ class VSWRAnalysis(Analysis):
def runAnalysis(self):
max_dips_shown = self.max_dips_shown
data = []
for d in self.app.data11:
data.append(d.vswr)
# min_idx = np.argmin(data)
@ -73,31 +92,17 @@ class VSWRAnalysis(Analysis):
# self.app.markers[0].setFrequency(str(self.app.data11[min_idx].freq))
# self.app.markers[0].frequencyInput.setText(str(self.app.data11[min_idx].freq))
minimums = []
min_start = -1
min_idx = -1
threshold = self.input_vswr_limit.value()
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
minimums = self.find_minimums(data, threshold)
logger.debug("Found %d sections under %f threshold", len(minimums), threshold)
logger.debug("Found %d sections under %f threshold",
len(minimums), threshold)
results_header = self.layout.indexOf(self.results_label)
logger.debug("Results start at %d, out of %d", results_header, self.layout.rowCount())
logger.debug("Results start at %d, out of %d",
results_header, self.layout.rowCount())
for i in range(results_header, self.layout.rowCount()):
self.layout.removeRow(self.layout.rowCount()-1)
self.layout.removeRow(self.layout.rowCount() - 1)
if len(minimums) > max_dips_shown:
self.layout.addRow(QtWidgets.QLabel("<b>More than " + str(max_dips_shown) +
@ -141,7 +146,341 @@ class VSWRAnalysis(Analysis):
format_frequency(self.app.data11[lowest].freq)))
self.layout.addWidget(PeakSearchAnalysis.QHLine())
# Remove the final separator line
self.layout.removeRow(self.layout.rowCount()-1)
self.layout.removeRow(self.layout.rowCount() - 1)
else:
self.layout.addRow(QtWidgets.QLabel(
"No areas found with VSWR below " + str(round(threshold, 2)) + "."))
class ResonanceAnalysis(Analysis):
# max_dips_shown = 3
@classmethod
def vswr_transformed(cls, z, ratio=49) -> float:
refl = reflection_coefficient(z / ratio)
mag = abs(refl)
if mag == 1:
return 1
return (1 + mag) / (1 - mag)
class QHLine(QtWidgets.QFrame):
def __init__(self):
super().__init__()
self.setFrameShape(QtWidgets.QFrame.HLine)
def __init__(self, app):
super().__init__(app)
self._widget = QtWidgets.QWidget()
self.layout = QtWidgets.QFormLayout()
self._widget.setLayout(self.layout)
self.input_description = QtWidgets.QLineEdit("")
self.checkbox_move_marker = QtWidgets.QCheckBox()
self.layout.addRow(QtWidgets.QLabel("<b>Settings</b>"))
self.layout.addRow("Description", self.input_description)
self.layout.addRow(VSWRAnalysis.QHLine())
self.layout.addRow(VSWRAnalysis.QHLine())
self.results_label = QtWidgets.QLabel("<b>Results</b>")
self.layout.addRow(self.results_label)
def _get_data(self, index):
my_data = {"freq": self.app.data11[index].freq,
"s11": self.app.data11[index].z,
"lambda": self.app.data11[index].wavelength,
"impedance": self.app.data11[index].impedance(),
"vswr": self.app.data11[index].vswr,
}
my_data["vswr_49"] = self.vswr_transformed(
my_data["impedance"], 49)
my_data["vswr_4"] = self.vswr_transformed(
my_data["impedance"], 4)
my_data["r"] = my_data["impedance"].real
my_data["x"] = my_data["impedance"].imag
return my_data
def _get_crossing(self):
data = []
for d in self.app.data11:
data.append(d.phase)
crossing = sorted(self.find_crossing_zero(data))
return crossing
def runAnalysis(self):
self.reset()
# self.results_label = QtWidgets.QLabel("<b>Results</b>")
# max_dips_shown = self.max_dips_shown
description = self.input_description.text()
if description:
filename = os.path.join("/tmp/", "{}.csv".format(description))
else:
filename = None
crossing = self._get_crossing()
logger.debug("Found %d sections ",
len(crossing))
results_header = self.layout.indexOf(self.results_label)
logger.debug("Results start at %d, out of %d",
results_header, self.layout.rowCount())
for i in range(results_header, self.layout.rowCount()):
self.layout.removeRow(self.layout.rowCount() - 1)
# if len(crossing) > max_dips_shown:
# self.layout.addRow(QtWidgets.QLabel("<b>More than " + str(max_dips_shown) +
# " dips found. Lowest shown.</b>"))
# self.crossing = crossing[:max_dips_shown]
extended_data = []
if len(crossing) > 0:
for m in crossing:
start, lowest, end = m
my_data = self._get_data(lowest)
extended_data.append(my_data)
if start != end:
logger.debug(
"Section from %d to %d, lowest at %d", start, end, lowest)
self.layout.addRow(
"Resonance",
QtWidgets.QLabel(
f"{format_frequency(self.app.data11[lowest].freq)}"
f" ({format_complex_imp(self.app.data11[lowest].impedance())})"))
else:
self.layout.addRow("Resonance", QtWidgets.QLabel(
format_frequency(self.app.data11[lowest].freq)))
self.layout.addWidget(PeakSearchAnalysis.QHLine())
# Remove the final separator line
self.layout.removeRow(self.layout.rowCount() - 1)
if filename and extended_data:
with open(filename, 'w', newline='') as csvfile:
fieldnames = extended_data[0].keys()
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for row in extended_data:
writer.writerow(row)
else:
self.layout.addRow(QtWidgets.QLabel(
"No resonance found"))
class EFHWAnalysis(ResonanceAnalysis):
'''
find only resonance when HI impedance
'''
old_data = []
def reset(self):
logger.debug("reset")
def runAnalysis(self):
self.reset()
# self.results_label = QtWidgets.QLabel("<b>Results</b>")
# max_dips_shown = self.max_dips_shown
description = self.input_description.text()
if description:
filename = os.path.join("/tmp/", "{}.csv".format(description))
else:
filename = None
crossing = self._get_crossing()
data = []
for d in self.app.data11:
data.append(d.impedance().real)
maximums = sorted(self.find_maximums(data, threshold=500))
results_header = self.layout.indexOf(self.results_label)
logger.debug("Results start at %d, out of %d",
results_header, self.layout.rowCount())
for i in range(results_header, self.layout.rowCount()):
self.layout.removeRow(self.layout.rowCount() - 1)
extended_data = OrderedDict()
#both = np.intersect1d([i[1] for i in crossing], maximums)
both = []
tolerance = 2
for i in maximums:
for l, _, h in crossing:
if l - tolerance <= i <= h + tolerance:
both.append(i)
continue
if l > i:
continue
if both:
logger.info("%i crossing HW", len(both))
logger.info(crossing)
logger.info(maximums)
logger.info(both)
for m in both:
my_data = self._get_data(m)
if m in extended_data:
extended_data[m].update(my_data)
else:
extended_data[m] = my_data
for i in range(min(len(both), len(self.app.markers))):
# self.app.markers[i].label = {}
# for l in TYPES:
# self.app.markers[i][l.label_id] = MarkerLabel(l.name)
# self.app.markers[i].label['actualfreq'].setMinimumWidth(
# 100)
# self.app.markers[i].label['returnloss'].setMinimumWidth(80)
self.app.markers[i].setFrequency(
str(self.app.data11[both[i]].freq))
self.app.markers[i].frequencyInput.setText(
str(self.app.data11[both[i]].freq))
else:
logger.info("TO DO: find near data")
for m in crossing:
start, lowest, end = m
my_data = self._get_data(lowest)
if lowest in extended_data:
extended_data[lowest].update(my_data)
else:
extended_data[lowest] = my_data
logger.debug("maximumx %s of type %s", maximums, type(maximums))
for m in maximums:
logger.debug("m %s of type %s", m, type(m))
my_data = self._get_data(m)
if m in extended_data:
extended_data[m].update(my_data)
else:
extended_data[m] = my_data
# saving and comparing
fields = [("freq", format_frequency_short),
("r", format_resistence_neg),
("lambda", round_2),
]
if self.old_data:
diff = self.compare(
self.old_data[-1], extended_data, fields=fields)
else:
diff = self.compare({}, extended_data, fields=fields)
self.old_data.append(extended_data)
for i, index in enumerate(sorted(extended_data.keys())):
self.layout.addRow(
f"{format_frequency_short(self.app.data11[index].freq)}",
QtWidgets.QLabel(f" ({diff[i]['freq']})"
f" {format_complex_imp(self.app.data11[index].impedance())}"
f" ({diff[i]['r']})"
f" {diff[i]['lambda']} m"))
# Remove the final separator line
# self.layout.removeRow(self.layout.rowCount() - 1)
if filename and extended_data:
with open(filename, 'w', newline='') as csvfile:
fieldnames = extended_data[sorted(
extended_data.keys())[0]].keys()
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for index in sorted(extended_data.keys()):
row = extended_data[index]
writer.writerow(row)
def compare(self, old, new, fields=[("freq", str), ]):
'''
Compare data to help changes
NB
must be same sweep
( same index must be same frequence )
:param old:
:param new:
'''
def no_compare():
return {k: "-" for k, _ in fields}
old_idx = sorted(old.keys())
# 'odict_keys' object is not subscriptable
new_idx = sorted(new.keys())
diff = {}
i_max = min(len(old_idx), len(new_idx))
i_tot = max(len(old_idx), len(new_idx))
if i_max == i_tot:
logger.debug("may be the same antenna ... analyzing")
else:
logger.warning("resonances changed from %s to %s",
len(old_idx), len(new_idx))
logger.debug("Trying to compare only first %s resonances", i_max)
split = 0
max_delta_f = 1000000 # 1M
for i, k in enumerate(new_idx):
my_diff = {}
logger.info("Risonance %s at %s", i,
format_frequency(new[k]["freq"]))
if len(old_idx) <= i + split:
diff[i] = no_compare()
continue
delta_f = new[k]["freq"] - old[old_idx[i + split]]["freq"]
if abs(delta_f) < max_delta_f:
logger.debug("can compare")
else:
logger.debug("can't compare, %s is too much ",
format_frequency(delta_f))
if delta_f > 0:
logger.debug("possible missing band, ")
if (len(old_idx) > (i + split + 1)):
if abs(new[k]["freq"] - old[old_idx[i + split + 1]]["freq"]) < max_delta_f:
logger.debug("new is missing band, compare next ")
split += 1
# FIXME: manage 2 or more band missing ?!?
else:
logger.debug("new band, non compare ")
diff[i] = no_compare()
continue
else:
logger.debug("new band, non compare ")
diff[i] = no_compare()
split -= 1
continue
for d, fn in fields:
my_diff[d] = fn(new[k][d] - old[old_idx[i + split]][d])
logger.info("Delta %s = %s", d,
my_diff[d])
diff[i] = my_diff
for i in range(i_max, i_tot):
# add missing in old ... if any
diff[i] = no_compare()
return diff

Wyświetl plik

@ -39,6 +39,7 @@ class Datapoint(NamedTuple):
@property
def z(self) -> complex:
""" return the datapoint impedance as complex number """
# FIXME: not impedance, but s11 ?
return complex(self.re, self.im)
@property
@ -158,7 +159,7 @@ def corr_att_data(data: List[Datapoint], att: float) -> List[Datapoint]:
if att <= 0:
return data
else:
att = 10**(att/20)
att = 10**(att / 20)
ndata = []
for dp in data:
corrected = dp.z * att

Wyświetl plik

@ -23,6 +23,9 @@ from PyQt5 import QtWidgets, QtCore
from NanoVNASaver.Analysis import Analysis, LowPassAnalysis, HighPassAnalysis, \
BandPassAnalysis, BandStopAnalysis, VSWRAnalysis, \
SimplePeakSearchAnalysis, MagLoopAnalysis
from NanoVNASaver.Analysis.VSWRAnalysis import ResonanceAnalysis
from NanoVNASaver.Analysis.VSWRAnalysis import EFHWAnalysis
from NanoVNASaver.Analysis import PeakSearchAnalysis
logger = logging.getLogger(__name__)
@ -46,14 +49,25 @@ class AnalysisWindow(QtWidgets.QWidget):
select_analysis_box = QtWidgets.QGroupBox("Select analysis")
select_analysis_layout = QtWidgets.QFormLayout(select_analysis_box)
self.analysis_list = QtWidgets.QComboBox()
self.analysis_list.addItem("Low-pass filter", LowPassAnalysis(self.app))
self.analysis_list.addItem("Band-pass filter", BandPassAnalysis(self.app))
self.analysis_list.addItem("High-pass filter", HighPassAnalysis(self.app))
self.analysis_list.addItem("Band-stop filter", BandStopAnalysis(self.app))
# self.analysis_list.addItem("Peak search", PeakSearchAnalysis(self.app))
self.analysis_list.addItem("Peak search", SimplePeakSearchAnalysis(self.app))
self.analysis_list.addItem(
"Low-pass filter", LowPassAnalysis(self.app))
self.analysis_list.addItem(
"Band-pass filter", BandPassAnalysis(self.app))
self.analysis_list.addItem(
"High-pass filter", HighPassAnalysis(self.app))
self.analysis_list.addItem(
"Band-stop filter", BandStopAnalysis(self.app))
self.analysis_list.addItem(
"Simple Peak search", SimplePeakSearchAnalysis(self.app))
self.analysis_list.addItem(
"Peak search", PeakSearchAnalysis(self.app))
self.analysis_list.addItem("VSWR analysis", VSWRAnalysis(self.app))
self.analysis_list.addItem("MagLoop analysis", MagLoopAnalysis(self.app))
self.analysis_list.addItem(
"Resonance analysis", ResonanceAnalysis(self.app))
self.analysis_list.addItem(
"HWEF analysis", EFHWAnalysis(self.app))
self.analysis_list.addItem(
"MagLoop analysis", MagLoopAnalysis(self.app))
select_analysis_layout.addRow("Analysis type", self.analysis_list)
self.analysis_list.currentIndexChanged.connect(self.updateSelection)
@ -61,8 +75,10 @@ class AnalysisWindow(QtWidgets.QWidget):
btn_run_analysis.clicked.connect(self.runAnalysis)
select_analysis_layout.addRow(btn_run_analysis)
self.checkbox_run_automatically = QtWidgets.QCheckBox("Run automatically")
self.checkbox_run_automatically.stateChanged.connect(self.toggleAutomaticRun)
self.checkbox_run_automatically = QtWidgets.QCheckBox(
"Run automatically")
self.checkbox_run_automatically.stateChanged.connect(
self.toggleAutomaticRun)
select_analysis_layout.addRow(self.checkbox_run_automatically)
analysis_box = QtWidgets.QGroupBox("Analysis")
@ -87,7 +103,8 @@ class AnalysisWindow(QtWidgets.QWidget):
old_item = self.analysis_layout.itemAt(0)
if old_item is not None:
old_widget = self.analysis_layout.itemAt(0).widget()
self.analysis_layout.replaceWidget(old_widget, self.analysis.widget())
self.analysis_layout.replaceWidget(
old_widget, self.analysis.widget())
old_widget.hide()
else:
self.analysis_layout.addWidget(self.analysis.widget())