Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding more features to TDR window #717

Merged
merged 6 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 78 additions & 47 deletions src/NanoVNASaver/Charts/TDR.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,27 @@

logger = logging.getLogger(__name__)

MIN_IMPEDANCE = 0
MAX_IMPEDANCE = 1000

MIN_S11 = -60
MAX_S11 = 0

MIN_VSWR = 1
MAX_VSWR = 10

class TDRChart(Chart):
maxDisplayLength = 50
minDisplayLength = 0
fixedSpan = False

minImpedance = 0
maxImpedance = 1000
minYlim = 0
maxYlim = 1000

decimals = 1

formatString = ""

fixedValues = False

markerLocation = -1
Expand Down Expand Up @@ -105,7 +118,7 @@ def __init__(self, name):
self.x_menu.addAction(self.action_set_fixed_start)
self.x_menu.addAction(self.action_set_fixed_stop)

self.y_menu = QMenu("Impedance axis")
self.y_menu = QMenu("Y axis")
self.y_mode_group = QActionGroup(self.y_menu)
self.y_action_automatic = QAction("Automatic")
self.y_action_automatic.setCheckable(True)
Expand All @@ -125,17 +138,17 @@ def __init__(self, name):
self.y_menu.addSeparator()

self.y_action_set_fixed_maximum = QAction(
f"Maximum ({self.maxImpedance})"
f"Maximum ({self.maxYlim})"
)
self.y_action_set_fixed_maximum.triggered.connect(
self.setMaximumImpedance
self.setMaximumY
)

self.y_action_set_fixed_minimum = QAction(
f"Minimum ({self.minImpedance})"
f"Minimum ({self.minYlim})"
)
self.y_action_set_fixed_minimum.triggered.connect(
self.setMinimumImpedance
self.setMinimumY
)

self.y_menu.addAction(self.y_action_set_fixed_maximum)
Expand All @@ -154,14 +167,15 @@ def __init__(self, name):
self.dim.width = self.width() - self.leftMargin - self.rightMargin
self.dim.height = self.height() - self.bottomMargin - self.topMargin


def contextMenuEvent(self, event):
self.action_set_fixed_start.setText(f"Start ({self.minDisplayLength})")
self.action_set_fixed_stop.setText(f"Stop ({self.maxDisplayLength})")
self.y_action_set_fixed_minimum.setText(
f"Minimum ({self.minImpedance})"
f"Minimum ({self.minYlim})"
)
self.y_action_set_fixed_maximum.setText(
f"Maximum ({self.maxImpedance})"
f"Maximum ({self.maxYlim})"
)
self.menu.exec(event.globalPos())

Expand All @@ -171,13 +185,30 @@ def isPlotable(self, x, y):
and self.topMargin <= y <= self.height() - self.bottomMargin
)

def _configureGraphFromFormat(self):
TDR_format = self.tdrWindow.format_dropdown.currentText()
if TDR_format == "|Z|":
self.minYlim = MIN_IMPEDANCE
self.maxYlim = MAX_IMPEDANCE
self.formatString = "impedance (\N{OHM SIGN})"
self.decimals = 1
elif TDR_format == "S11":
self.minYlim = MIN_S11
self.maxYlim = MAX_S11
self.formatString = "S11 (dB)"
self.decimals = 1
elif TDR_format == "VSWR":
self.minYlim = MIN_VSWR
self.maxYlim = MAX_VSWR
self.formatString = "VSWR"
self.decimals = 2

def resetDisplayLimits(self):
self._configureGraphFromFormat()
self.fixedSpan = False
self.minDisplayLength = 0
self.maxDisplayLength = 100
self.fixedValues = False
self.minImpedance = 0
self.maxImpedance = 1000
self.update()

def setFixedSpan(self, fixed_span):
Expand Down Expand Up @@ -205,7 +236,7 @@ def setMaximumLength(self):
self,
"Stop length (m)",
"Set stop length (m)",
value=self.minDisplayLength,
value=self.maxDisplayLength,
min=0.1,
decimals=1,
)
Expand All @@ -220,35 +251,33 @@ def setFixedValues(self, fixed_values):
self.fixedValues = fixed_values
self.update()

def setMinimumImpedance(self):
def setMinimumY(self):
min_val, selected = QInputDialog.getDouble(
self,
"Minimum impedance (\N{OHM SIGN})",
"Set minimum impedance (\N{OHM SIGN})",
value=self.minDisplayLength,
min=0,
decimals=1,
"Minimum " + self.formatString,
"Set minimum "+ self.formatString,
value=self.minYlim,
decimals=self.decimals,
)
if not selected:
return
if not (self.fixedValues and min_val >= self.maxImpedance):
self.minImpedance = min_val
if not (self.fixedValues and min_val >= self.maxYlim):
self.minYlim = min_val
if self.fixedValues:
self.update()

def setMaximumImpedance(self):
def setMaximumY(self):
max_val, selected = QInputDialog.getDouble(
self,
"Maximum impedance (\N{OHM SIGN})",
"Set maximum impedance (\N{OHM SIGN})",
value=self.minDisplayLength,
min=0.1,
decimals=1,
"Maximum "+ self.formatString,
"Set maximum "+ self.formatString,
value=self.maxYlim,
decimals=self.decimals,
)
if not selected:
return
if not (self.fixedValues and max_val <= self.minImpedance):
self.maxImpedance = max_val
if not (self.fixedValues and max_val <= self.minYlim):
self.maxYlim = max_val
if self.fixedValues:
self.update()

Expand All @@ -258,8 +287,8 @@ def copy(self):
new_chart.minDisplayLength = self.minDisplayLength
new_chart.maxDisplayLength = self.maxDisplayLength
new_chart.fixedSpan = self.fixedSpan
new_chart.minImpedance = self.minImpedance
new_chart.maxImpedance = self.maxImpedance
new_chart.minYlim = self.minYlim
new_chart.maxYlim = self.maxYlim
new_chart.fixedValues = self.fixedValues
self.tdrWindow.updated.connect(new_chart.update)
return new_chart
Expand Down Expand Up @@ -337,7 +366,7 @@ def _draw_ticks(self, height, width, x_step, min_index):
qp.drawText(
self.leftMargin - 10,
self.topMargin + height + 15,
f"{str(round(self.tdrWindow.distance_axis[min_index] / 2, 1))}m",
f"{str(round(self.tdrWindow.distance_axis[min_index] / 2, self.decimals))}m",
)

def _draw_y_ticks(self, height, width, min_impedance, max_impedance):
Expand All @@ -351,10 +380,10 @@ def _draw_y_ticks(self, height, width, min_impedance, max_impedance):
qp.drawLine(self.leftMargin, y, self.leftMargin + width, y)
y_val = max_impedance - y_step * i * y_tick_step
qp.setPen(Chart.color.text)
qp.drawText(3, y + 3, str(round(y_val, 1)))
qp.drawText(3, y + 3, str(round(y_val, self.decimals)))
qp.setPen(Chart.color.text)
qp.drawText(
3, self.topMargin + height + 3, f"{round(min_impedance, 1)}"
3, self.topMargin + height + 3, f"{round(min_impedance, self.decimals)}"
)

def _draw_max_point(self, height, x_step, y_step, min_index):
Expand Down Expand Up @@ -412,11 +441,14 @@ def _draw_graph(self, height, width):
x_step = (max_index - min_index) / width

# TODO: Limit the search to the selected span?
min_impedance = max(0, np.min(self.tdrWindow.step_response_Z) / 1.05)
max_impedance = min(1000, np.max(self.tdrWindow.step_response_Z) * 1.05)
min_Z = np.min(self.tdrWindow.step_response_Z)
max_Z = np.max(self.tdrWindow.step_response_Z)
# Ensure that everything works even if limits are negative
min_impedance = max(self.minYlim, min_Z - 0.05 * np.abs(min_Z))
max_impedance = min(self.maxYlim, max_Z + 0.05 * np.abs(max_Z))
if self.fixedValues:
min_impedance = max(0, self.minImpedance)
max_impedance = max(0.1, self.maxImpedance)
min_impedance = self.minYlim
max_impedance = self.maxYlim

y_step = max(self.tdrWindow.td) * 1.1 / height or 1.0e-30

Expand Down Expand Up @@ -495,15 +527,14 @@ def valueAtPosition(self, y):
height = self.height() - self.topMargin - self.bottomMargin
absy = (self.height() - y) - self.bottomMargin
if self.fixedValues:
min_impedance = self.minImpedance
max_impedance = self.maxImpedance
min_impedance = self.minYlim
max_impedance = self.maxYlim
else:
min_impedance = max(
0, np.min(self.tdrWindow.step_response_Z) / 1.05
)
max_impedance = min(
1000, np.max(self.tdrWindow.step_response_Z) * 1.05
)
min_Z = np.min(self.tdrWindow.step_response_Z)
max_Z = np.max(self.tdrWindow.step_response_Z)
# Ensure that everything works even if limits are negative
min_impedance = max(self.minYlim, min_Z - 0.05 * np.abs(min_Z))
max_impedance = min(self.maxYlim, max_Z + 0.05 * np.abs(max_Z))
y_step = (max_impedance - min_impedance) / height
return y_step * absy + min_impedance
return 0
Expand Down Expand Up @@ -540,8 +571,8 @@ def zoomTo(self, x1, y1, x2, y2):
val2 = self.valueAtPosition(y2)

if val1 != val2:
self.minImpedance = round(min(val1, val2), 3)
self.maxImpedance = round(max(val1, val2), 3)
self.minYlim = round(min(val1, val2), 3)
self.maxYlim = round(max(val1, val2), 3)
self.setFixedValues(True)

len1 = max(0, self.lengthAtPosition(x1, limit=False))
Expand Down
41 changes: 32 additions & 9 deletions src/NanoVNASaver/Windows/TDR.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,28 @@ def __init__(self, app: QtWidgets.QWidget):
layout = QtWidgets.QFormLayout()
make_scrollable(self, layout)

dropdown_layout = QtWidgets.QHBoxLayout()

self.tdr_velocity_dropdown = QtWidgets.QComboBox()
for cable_name, velocity in CABLE_PARAMETERS:
self.tdr_velocity_dropdown.addItem(cable_name, velocity)
self.tdr_velocity_dropdown.insertSeparator(
self.tdr_velocity_dropdown.count()
)
self.tdr_velocity_dropdown.insertSeparator(self.tdr_velocity_dropdown.count())
self.tdr_velocity_dropdown.addItem("Custom", -1)
self.tdr_velocity_dropdown.setCurrentIndex(1) # Default to PE (0.66)
self.tdr_velocity_dropdown.currentIndexChanged.connect(self.updateTDR)
layout.addRow(self.tdr_velocity_dropdown)

dropdown_layout.addWidget(self.tdr_velocity_dropdown)

self.format_dropdown = QtWidgets.QComboBox()
self.format_dropdown.addItem("|Z|")
self.format_dropdown.addItem("S11")
self.format_dropdown.addItem("VSWR")

self.format_dropdown.currentIndexChanged.connect(self.updateFormat)

dropdown_layout.addWidget(self.format_dropdown)

layout.addRow(dropdown_layout)

self.tdr_velocity_input = QtWidgets.QLineEdit()
self.tdr_velocity_input.setDisabled(True)
Expand All @@ -111,6 +123,10 @@ def __init__(self, app: QtWidgets.QWidget):

layout.addRow(self.app.tdr_chart)

def updateFormat(self):
self.app.tdr_chart.resetDisplayLimits()
self.updateTDR()

def updateTDR(self):
# TODO: Let the user select whether to use high or low resolution TDR?
FFT_POINTS = 2**14
Expand Down Expand Up @@ -138,24 +154,31 @@ def updateTDR(self):
return

s11 = [complex(d.re, d.im) for d in self.app.data.s11]

s11 = np.array(s11)
s11 = np.concatenate([s11, np.conj(s11[-1:0:-1])]) # Include negative frequencies
s11 = np.fft.fftshift(s11)

window = np.blackman(len(s11))
windowed_s11 = window * s11 # Now windowing eliminates higher frequencies while leaving low frequencies untouched
windowed_s11 = window * s11 #Now windowing eliminates higher frequencies while leaving low frequencies untouched

pad_points = (FFT_POINTS - len(windowed_s11)) // 2
windowed_s11 = np.pad(windowed_s11, [pad_points + 1, pad_points]) # Pad array to length FFT_POINTS
windowed_s11 = np.fft.ifftshift(windowed_s11)

td = np.fft.ifft(windowed_s11)

step = np.ones(FFT_POINTS)
step_response = convolve(td, step)

self.step_response_Z = np.abs(50 * (1 + step_response) / (1 - step_response)) #Can plot impedance in terms of real and imaginary too

# calculate step response based on the format that the user selected
TDR_format = self.format_dropdown.currentText();
step_Z = 50 * (1 + step_response) / (1 - step_response)
step_refl_coefficient = np.abs((step_Z - 50)/(step_Z + 50))
if TDR_format == "|Z|":
self.step_response_Z = np.abs(step_Z)
elif TDR_format == "S11":
self.step_response_Z = 20 * np.log10(step_refl_coefficient)
elif TDR_format == "VSWR":
self.step_response_Z = np.abs((1 + step_refl_coefficient)/(1 - step_refl_coefficient))
time_axis = np.linspace(0, 1 / step_size, FFT_POINTS)
self.distance_axis = time_axis * v * speed_of_light
# peak = np.max(td)
Expand Down
Loading