|
@@ -1,65 +1,19 @@
|
|
-from streamlit_ace import st_ace
|
|
|
|
-from streamlit_echarts import st_echarts, JsCode
|
|
|
|
import math
|
|
import math
|
|
import streamlit as st
|
|
import streamlit as st
|
|
import matplotlib.pyplot as plt
|
|
import matplotlib.pyplot as plt
|
|
import numpy as np
|
|
import numpy as np
|
|
|
|
+import sigfig
|
|
|
|
+import pyperclip
|
|
|
|
+from streamlit_ace import st_ace
|
|
|
|
+from streamlit_echarts import st_echarts, JsCode
|
|
|
|
|
|
-def circle(ax, x, y, radius, color='#1946BA'):
|
|
|
|
- from matplotlib.patches import Ellipse
|
|
|
|
- drawn_circle = Ellipse((x, y), radius * 2, radius * 2, clip_on=True,
|
|
|
|
- zorder=2, linewidth=2, edgecolor=color, facecolor=(0, 0, 0, .0125))
|
|
|
|
- try:
|
|
|
|
- ax.add_artist(drawn_circle)
|
|
|
|
- except:
|
|
|
|
- raise
|
|
|
|
-
|
|
|
|
-def plot_smith(r, i, g, r_cut, i_cut):
|
|
|
|
- fig = plt.figure(figsize=(10, 10))
|
|
|
|
- ax = fig.add_subplot()
|
|
|
|
-
|
|
|
|
- major_ticks = np.arange(-1.0, 1.1, 0.25)
|
|
|
|
- minor_ticks = np.arange(-1.1, 1.1, 0.05)
|
|
|
|
- # ax.set_xticks(major_ticks)
|
|
|
|
- ax.set_xticks(minor_ticks, minor=True)
|
|
|
|
- # ax.set_yticks(major_ticks)
|
|
|
|
- ax.set_yticks(minor_ticks, minor=True)
|
|
|
|
- ax.grid(which='major', color='grey', linewidth=1.5)
|
|
|
|
- ax.grid(which='minor', color='grey', linewidth=0.5, linestyle=':')
|
|
|
|
- plt.xlabel(r'$Re(\Gamma)$', color='gray', fontsize=16, fontname="Cambria")
|
|
|
|
- plt.ylabel('$Im(\Gamma)$', color='gray', fontsize=16, fontname="Cambria")
|
|
|
|
- plt.title('Smith chart', fontsize=24, fontname="Cambria")
|
|
|
|
-
|
|
|
|
- # unit circle
|
|
|
|
- circle(ax, 0, 0, 1)
|
|
|
|
-
|
|
|
|
- # input data points
|
|
|
|
- ax.plot(r, i, '+', ms=8, mew=2, color='#b6c7f4')
|
|
|
|
-
|
|
|
|
- # choosen data points
|
|
|
|
- ax.plot(r_cut, i_cut, '+', ms=8, mew=2, color='#1946BA')
|
|
|
|
-
|
|
|
|
- # circle approximation by calc
|
|
|
|
- radius = abs(g[1] - g[0] / g[2]) / 2
|
|
|
|
- x = ((g[1] + g[0] / g[2]) / 2).real
|
|
|
|
- y = ((g[1] + g[0] / g[2]) / 2).imag
|
|
|
|
- circle(ax, x, y, radius, color='#FF8400')
|
|
|
|
-
|
|
|
|
- XLIM = [-1.1, 1.1]
|
|
|
|
- YLIM = [-1.1, 1.1]
|
|
|
|
- ax.set_xlim(XLIM)
|
|
|
|
- ax.set_ylim(YLIM)
|
|
|
|
- st.pyplot(fig)
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-
|
|
|
|
-interval_range = (0, 100) # in percents
|
|
|
|
-interval_start, interval_end = 0, 0
|
|
|
|
|
|
|
|
|
|
+# So that you can choose an interval of points on which we apply q-calc algorithm
|
|
|
|
+def plot_interact_abs_from_f(f, r, i, interval_range):
|
|
|
|
+ if interval_range is None:
|
|
|
|
+ interval_range = (0, 100)
|
|
|
|
|
|
-def plot_interact_abs_from_f(f, r, i):
|
|
|
|
- abs_S = list((r[n] ** 2 + i[n] ** 2)**0.5 for n in range(len(r)))
|
|
|
|
- global interval_range, interval_start, interval_end
|
|
|
|
|
|
+ abs_S = list(abs(np.array(r) + 1j * np.array(i)))
|
|
# echarts for datazoom https://discuss.streamlit.io/t/streamlit-echarts/3655
|
|
# echarts for datazoom https://discuss.streamlit.io/t/streamlit-echarts/3655
|
|
# datazoom https://echarts.apache.org/examples/en/editor.html?c=line-draggable&lang=ts
|
|
# datazoom https://echarts.apache.org/examples/en/editor.html?c=line-draggable&lang=ts
|
|
# axis pointer values https://echarts.apache.org/en/option.html#axisPointer
|
|
# axis pointer values https://echarts.apache.org/en/option.html#axisPointer
|
|
@@ -88,7 +42,7 @@ def plot_interact_abs_from_f(f, r, i):
|
|
},
|
|
},
|
|
"series": [{"data": abs_S, "type": "line", "name": "abs(S)"}],
|
|
"series": [{"data": abs_S, "type": "line", "name": "abs(S)"}],
|
|
"height": 300,
|
|
"height": 300,
|
|
- "dataZoom": [{"type": "slider", "start": 0, "end": 100, "height": 100, "bottom": 10}],
|
|
|
|
|
|
+ "dataZoom": [{"type": "slider", "start": interval_range[0], "end": interval_range[1], "height": 100, "bottom": 10}],
|
|
"tooltip": {
|
|
"tooltip": {
|
|
"trigger": "axis",
|
|
"trigger": "axis",
|
|
"axisPointer": {
|
|
"axisPointer": {
|
|
@@ -108,24 +62,76 @@ def plot_interact_abs_from_f(f, r, i):
|
|
}
|
|
}
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
+ # DataZoom event is not fired on new file upload. There are no default event to fix it.
|
|
events = {
|
|
events = {
|
|
- "dataZoom": "function(params) { return [params.start, params.end] }",
|
|
|
|
|
|
+ "dataZoom": "function(params) { return ['dataZoom', params.start, params.end] }",
|
|
|
|
+ "restore": "function() { return ['restore'] }",
|
|
}
|
|
}
|
|
|
|
|
|
# show echart with dataZoom and update intervals based on output
|
|
# show echart with dataZoom and update intervals based on output
|
|
- interval_range = st_echarts(
|
|
|
|
|
|
+
|
|
|
|
+ get_event = st_echarts(
|
|
options=options, events=events, height="500px", key="render_basic_bar_events"
|
|
options=options, events=events, height="500px", key="render_basic_bar_events"
|
|
)
|
|
)
|
|
|
|
|
|
- if interval_range is None:
|
|
|
|
- interval_range = (0, 100)
|
|
|
|
|
|
+ if not get_event is None and get_event[0] == 'dataZoom':
|
|
|
|
+ interval_range = get_event[1:]
|
|
|
|
|
|
n = len(f)
|
|
n = len(f)
|
|
interval_start, interval_end = (
|
|
interval_start, interval_end = (
|
|
int(n*interval_range[id]*0.01) for id in (0, 1))
|
|
int(n*interval_range[id]*0.01) for id in (0, 1))
|
|
|
|
+ return interval_range, interval_start, interval_end
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def circle(ax, x, y, radius, color='#1946BA'):
|
|
|
|
+ from matplotlib.patches import Ellipse
|
|
|
|
+ drawn_circle = Ellipse((x, y), radius * 2, radius * 2, clip_on=True,
|
|
|
|
+ zorder=2, linewidth=2, edgecolor=color, facecolor=(0, 0, 0, .0125))
|
|
|
|
+ ax.add_artist(drawn_circle)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def plot_smith(r, i, g, r_cut, i_cut, show_excluded):
|
|
|
|
+ fig = plt.figure(figsize=(10, 10))
|
|
|
|
+ ax = fig.add_subplot()
|
|
|
|
+
|
|
|
|
+ # major_ticks = np.arange(-1.0, 1.1, 0.25)
|
|
|
|
+ minor_ticks = np.arange(-1.1, 1.1, 0.05)
|
|
|
|
+ # ax.set_xticks(major_ticks)
|
|
|
|
+ ax.set_xticks(minor_ticks, minor=True)
|
|
|
|
+ # ax.set_yticks(major_ticks)
|
|
|
|
+ ax.set_yticks(minor_ticks, minor=True)
|
|
|
|
+ ax.grid(which='major', color='grey', linewidth=1.5)
|
|
|
|
+ ax.grid(which='minor', color='grey', linewidth=0.5, linestyle=':')
|
|
|
|
+ plt.xlabel('$Re(\Gamma)$', color='gray', fontsize=16, fontname="Cambria")
|
|
|
|
+ plt.ylabel('$Im(\Gamma)$', color='gray', fontsize=16, fontname="Cambria")
|
|
|
|
+ plt.title('Smith chart', fontsize=24, fontname="Cambria")
|
|
|
|
+
|
|
|
|
+ # unit circle
|
|
|
|
+ circle(ax, 0, 0, 1)
|
|
|
|
+
|
|
|
|
+ # input data points
|
|
|
|
+ if show_excluded:
|
|
|
|
+ ax.plot(r, i, '+', ms=8, mew=2, color='#b6c7f4')
|
|
|
|
+
|
|
|
|
+ # choosen data points
|
|
|
|
+ ax.plot(r_cut, i_cut, '+', ms=8, mew=2, color='#1946BA')
|
|
|
|
+
|
|
|
|
+ # circle approximation by calc
|
|
|
|
+ radius = abs(g[1] - g[0] / g[2]) / 2
|
|
|
|
+ x = ((g[1] + g[0] / g[2]) / 2).real
|
|
|
|
+ y = ((g[1] + g[0] / g[2]) / 2).imag
|
|
|
|
+ circle(ax, x, y, radius, color='#FF8400')
|
|
|
|
+
|
|
|
|
+ XLIM = [-1.1, 1.1]
|
|
|
|
+ YLIM = [-1.1, 1.1]
|
|
|
|
+ ax.set_xlim(XLIM)
|
|
|
|
+ ax.set_ylim(YLIM)
|
|
|
|
+ st.pyplot(fig)
|
|
|
|
+
|
|
|
|
|
|
# plot (abs(S))(f) chart with pyplot
|
|
# plot (abs(S))(f) chart with pyplot
|
|
-def plot_ref_from_f(f, r, i):
|
|
|
|
|
|
+def plot_abs_vs_f(f, r, i):
|
|
fig = plt.figure(figsize=(10, 10))
|
|
fig = plt.figure(figsize=(10, 10))
|
|
abs_S = list((r[n] ** 2 + i[n] ** 2)**0.5 for n in range(len(r)))
|
|
abs_S = list((r[n] ** 2 + i[n] ** 2)**0.5 for n in range(len(r)))
|
|
xlim = [min(f) - abs(max(f) - min(f)) * 0.1,
|
|
xlim = [min(f) - abs(max(f) - min(f)) * 0.1,
|
|
@@ -139,10 +145,14 @@ def plot_ref_from_f(f, r, i):
|
|
ax.grid(which='minor', color='grey', linestyle=':', linewidth=0.5)
|
|
ax.grid(which='minor', color='grey', linestyle=':', linewidth=0.5)
|
|
plt.xlabel(r'$f,\; 1/c$', color='gray', fontsize=16, fontname="Cambria")
|
|
plt.xlabel(r'$f,\; 1/c$', color='gray', fontsize=16, fontname="Cambria")
|
|
plt.ylabel('$|S|$', color='gray', fontsize=16, fontname="Cambria")
|
|
plt.ylabel('$|S|$', color='gray', fontsize=16, fontname="Cambria")
|
|
- plt.title('Absolute value of reflection coefficient from frequency',
|
|
|
|
|
|
+ plt.title('Abs(S) vs frequency',
|
|
fontsize=24, fontname="Cambria")
|
|
fontsize=24, fontname="Cambria")
|
|
|
|
|
|
ax.plot(f, abs_S, '+', ms=8, mew=2, color='#1946BA')
|
|
ax.plot(f, abs_S, '+', ms=8, mew=2, color='#1946BA')
|
|
|
|
+
|
|
|
|
+ # radius = abs(g[1] - g[0] / g[2]) / 2
|
|
|
|
+ # x = ((g[1] + g[0] / g[2]) / 2).real
|
|
|
|
+ # y = ((g[1] + g[0] / g[2]) / 2).imag
|
|
st.pyplot(fig)
|
|
st.pyplot(fig)
|
|
|
|
|
|
|
|
|
|
@@ -173,88 +183,176 @@ def run(calc_function):
|
|
nonlocal data_format_snp
|
|
nonlocal data_format_snp
|
|
if data_format_snp:
|
|
if data_format_snp:
|
|
for x in range(len(data)):
|
|
for x in range(len(data)):
|
|
- if data[x][0]=='#':
|
|
|
|
|
|
+ if data[x][0] == '#':
|
|
line = data[x].split()
|
|
line = data[x].split()
|
|
- if len(line)== 6:
|
|
|
|
- repr_map = {"RI":0,"MA":1, "DB":2}
|
|
|
|
- para_map = {"S":0,"Z":1}
|
|
|
|
- hz_map = {"GHz":10**9,"MHz":10**6,"KHz":10**3,"Hz":1}
|
|
|
|
- hz,measurement_parameter,data_representation,_r,ref_resistance=line[1:]
|
|
|
|
|
|
+ if len(line) == 6:
|
|
|
|
+ repr_map = {"RI": 0, "MA": 1, "DB": 2}
|
|
|
|
+ para_map = {"S": 0, "Z": 1}
|
|
|
|
+ hz_map = {"GHz": 10**9, "MHz": 10 **6, "KHz": 10**3, "Hz": 1}
|
|
|
|
+ hz, measurement_parameter, data_representation, _r, ref_resistance = line[1:]
|
|
try:
|
|
try:
|
|
- return hz_map[hz], para_map[measurement_parameter], repr_map[data_representation], ref_resistance
|
|
|
|
|
|
+ return hz_map[hz], para_map[measurement_parameter], repr_map[data_representation], int(ref_resistance)
|
|
except:
|
|
except:
|
|
break
|
|
break
|
|
break
|
|
break
|
|
return 1, 0, 0, 50
|
|
return 1, 0, 0, 50
|
|
-
|
|
|
|
|
|
|
|
- def unpack_data(data, input_start_line, input_end_line):
|
|
|
|
|
|
+ # check if line has comments
|
|
|
|
+ # first is a comment line according to .snp documentation,
|
|
|
|
+ # others detects comments in various languages
|
|
|
|
+ def check_line_comments(line):
|
|
|
|
+ if len(line) < 2 or line[0] == '!' or line[0] == '#' or line[0] == '%' or line[0] == '/':
|
|
|
|
+ return None
|
|
|
|
+ else:
|
|
|
|
+ # generally we expect these chars as separators
|
|
|
|
+ line = line.replace(';', ' ').replace(',', ' ')
|
|
|
|
+ if '!' in line:
|
|
|
|
+ line = line[:line.find('!')]
|
|
|
|
+ return line
|
|
|
|
+
|
|
|
|
+ # unpack a few first lines of the file to get number of ports
|
|
|
|
+ def count_columns(data):
|
|
|
|
+ return_status = 'data parsed'
|
|
|
|
+ column_count = 0
|
|
|
|
+ for x in range(len(data)):
|
|
|
|
+ line = check_line_comments(data[x])
|
|
|
|
+ if line is None:
|
|
|
|
+ continue
|
|
|
|
+ line = line.split()
|
|
|
|
+ # always at least 3 values for single data point
|
|
|
|
+ if len(line) < 3:
|
|
|
|
+ return_status = 'Can\'t parse line № ' + \
|
|
|
|
+ str(x) + ',\n not enough arguments (less than 3)'
|
|
|
|
+ break
|
|
|
|
+ column_count = len(line)
|
|
|
|
+ break
|
|
|
|
+ return column_count, return_status
|
|
|
|
+
|
|
|
|
+ def unpack_data(data, first_column, column_count, ref_resistance, ace_preview_markers):
|
|
nonlocal select_measurement_parameter
|
|
nonlocal select_measurement_parameter
|
|
nonlocal select_data_representation
|
|
nonlocal select_data_representation
|
|
f, r, i = [], [], []
|
|
f, r, i = [], [], []
|
|
- return_status='data parsed'
|
|
|
|
- for x in range(input_start_line-1, input_end_line):
|
|
|
|
- if len(data[x])<2 or data[x][0]== '!' or data[x][0]=='#' or data[x][0]=='%' or data[x][0]=='/':
|
|
|
|
- # first is a comment line according to .snp documentation,
|
|
|
|
- # others detects comments in various languages
|
|
|
|
|
|
+ return_status = 'data parsed'
|
|
|
|
+ for x in range(len(data)):
|
|
|
|
+ line = check_line_comments(data[x])
|
|
|
|
+ if line is None:
|
|
continue
|
|
continue
|
|
- data[x] = data[x].replace(';', ' ').replace(',', ' ') # generally we expect these chars as seperators
|
|
|
|
- line = data[x].split()
|
|
|
|
|
|
|
|
- # always at least 3 values for single data point
|
|
|
|
- if len(line) < 3:
|
|
|
|
- return_status = 'Can\'t parse line №: ' + str(x) + ',\n not enough arguments (less than 3)'
|
|
|
|
|
|
+ line = line.split()
|
|
|
|
+
|
|
|
|
+ if column_count != len(line):
|
|
|
|
+ return_status = "Wrong number of parameters on line № " + str(x)
|
|
break
|
|
break
|
|
|
|
|
|
# 1: process according to data_placement
|
|
# 1: process according to data_placement
|
|
- a,b,c=[],[],[]
|
|
|
|
|
|
+ a, b, c = None, None, None
|
|
try:
|
|
try:
|
|
- a, b, c = (line[y] for y in range(min(len(line),3)))
|
|
|
|
- # for x in input_data_columns.keys():
|
|
|
|
- # if x=='f':
|
|
|
|
-
|
|
|
|
- # elif x=='r':
|
|
|
|
-
|
|
|
|
- # elif x=='i':
|
|
|
|
|
|
+ a = line[0]
|
|
|
|
+ b = line[first_column]
|
|
|
|
+ c = line[first_column+1]
|
|
except:
|
|
except:
|
|
- return_status = 'Can\'t parse line №: ' + str(x) + ',\n not enough arguments'
|
|
|
|
- break
|
|
|
|
|
|
+ return_status = 'Can\'t parse line №: ' + \
|
|
|
|
+ str(x) + ',\n not enough arguments'
|
|
|
|
+ break
|
|
if not ((is_float(a)) or (is_float(b)) or (is_float(c))):
|
|
if not ((is_float(a)) or (is_float(b)) or (is_float(c))):
|
|
- return_status = 'Wrong data type, expected number. Error on line: ' + str(x)
|
|
|
|
|
|
+ return_status = 'Wrong data type, expected number. Error on line: ' + \
|
|
|
|
+ str(x)
|
|
break
|
|
break
|
|
- a,b,c=(float(x) for x in (a,b,c))
|
|
|
|
|
|
+
|
|
|
|
+ # mark as processed
|
|
|
|
+ for y in (a,b,c):
|
|
|
|
+ ace_preview_markers.append(
|
|
|
|
+ {"startRow": x,"startCol": 0,
|
|
|
|
+ "endRow": x,"endCol": data[x].find(y)+len(y),
|
|
|
|
+ "className": "ace_stack","type": "text"})
|
|
|
|
+
|
|
|
|
+ a, b, c = (float(x) for x in (a, b, c))
|
|
f.append(a) # frequency
|
|
f.append(a) # frequency
|
|
|
|
|
|
# 2: process according to data_representation
|
|
# 2: process according to data_representation
|
|
if select_data_representation == 'Frequency, real, imaginary':
|
|
if select_data_representation == 'Frequency, real, imaginary':
|
|
|
|
+ # std format
|
|
r.append(b) # Re
|
|
r.append(b) # Re
|
|
i.append(c) # Im
|
|
i.append(c) # Im
|
|
elif select_data_representation == 'Frequency, magnitude, angle':
|
|
elif select_data_representation == 'Frequency, magnitude, angle':
|
|
- r.append(b*np.cos(np.deg2rad(c))) # Re
|
|
|
|
- i.append(b*np.sin(np.deg2rad(c))) # Im
|
|
|
|
|
|
+ r.append(b*np.cos(np.deg2rad(c)))
|
|
|
|
+ i.append(b*np.sin(np.deg2rad(c)))
|
|
elif select_data_representation == 'Frequency, db, angle':
|
|
elif select_data_representation == 'Frequency, db, angle':
|
|
- b=10**(b/20)
|
|
|
|
- r.append(b*np.sin(np.deg2rad(c))) # Re
|
|
|
|
- i.append(b*np.cos(np.deg2rad(c))) # Im
|
|
|
|
|
|
+ b = 10**(b/20)
|
|
|
|
+ r.append(b*np.cos(np.deg2rad(c)))
|
|
|
|
+ i.append(b*np.sin(np.deg2rad(c)))
|
|
else:
|
|
else:
|
|
return_status = 'Wrong data format'
|
|
return_status = 'Wrong data format'
|
|
break
|
|
break
|
|
|
|
|
|
# 3: process according to measurement_parameter
|
|
# 3: process according to measurement_parameter
|
|
|
|
+ if select_measurement_parameter == 'Z':
|
|
|
|
+ # normalization
|
|
|
|
+ r[-1] = r[-1]/ref_resistance
|
|
|
|
+ i[-1] = i[-1]/ref_resistance
|
|
|
|
+
|
|
|
|
+ # translate to S
|
|
|
|
+ try:
|
|
|
|
+ # center_x + 1j*center_y, radius
|
|
|
|
+ p1, r1 = r[-1] / (1 + r[-1]) + 0j, 1 / (1 + r[-1]) #real
|
|
|
|
+ p2, r2 = 1 + 1j * (1 / i[-1]), 1 / i[-1] #imag
|
|
|
|
+
|
|
|
|
+ d = abs(p2-p1)
|
|
|
|
+ q = (r1**2 - r2**2 + d**2) / (2 * d)
|
|
|
|
+
|
|
|
|
+ h = (r1**2 - q**2)**0.5
|
|
|
|
+
|
|
|
|
+ p = p1 + q * (p2 - p1) / d
|
|
|
|
+
|
|
|
|
+ intersect = [
|
|
|
|
+ (p.real + h * (p2.imag - p1.imag) / d,
|
|
|
|
+ p.imag - h * (p2.real - p1.real) / d),
|
|
|
|
+ (p.real - h * (p2.imag - p1.imag) / d,
|
|
|
|
+ p.imag + h * (p2.real - p1.real) / d)]
|
|
|
|
+
|
|
|
|
+ intersect = [x+1j*y for x,y in intersect]
|
|
|
|
+ intersect_shift = [p-(1+0j) for p in intersect]
|
|
|
|
+ intersect_shift = abs(np.array(intersect_shift))
|
|
|
|
+ p=intersect[0]
|
|
|
|
+ if intersect_shift[0]<intersect_shift[1]:
|
|
|
|
+ p=intersect[1]
|
|
|
|
+ r[-1] = p.real
|
|
|
|
+ i[-1] = p.imag
|
|
|
|
+ except:
|
|
|
|
+ r.pop()
|
|
|
|
+ i.pop()
|
|
|
|
+ f.pop()
|
|
|
|
+
|
|
|
|
+ if len(f) < 3 or len(f) != len(r) or len(f) != len(i):
|
|
|
|
+ return_status = 'Choosen data range is too small, add more points'
|
|
|
|
+ elif max(abs(np.array(r)+ 1j* np.array(i))) > 2:
|
|
|
|
+ return_status = 'Your data points have an abnormality:\
|
|
|
|
+ they are too far outlise the unit cirlce.\
|
|
|
|
+ Make sure the format is correct'
|
|
|
|
|
|
return f, r, i, return_status
|
|
return f, r, i, return_status
|
|
|
|
|
|
- # make accessible specific range of numerical data choosen with interactive plot
|
|
|
|
- global interval_range, interval_start, interval_end
|
|
|
|
|
|
+ # make accessible a specific range of numerical data choosen with interactive plot
|
|
|
|
+ # percent, line id, line id
|
|
|
|
+ interval_range, interval_start, interval_end = None, None, None
|
|
|
|
+
|
|
|
|
+ # info
|
|
|
|
+ with st.expander("Info"):
|
|
|
|
+ # streamlit.markdown does not support footnotes
|
|
|
|
+ try:
|
|
|
|
+ with open('./source/frontend/info.md') as f:
|
|
|
|
+ st.markdown(f.read())
|
|
|
|
+ except:
|
|
|
|
+ st.write('Wrong start directory, see readme')
|
|
|
|
|
|
# file upload button
|
|
# file upload button
|
|
- uploaded_file = st.file_uploader('Upload a file from your vector analizer. ' +
|
|
|
|
- 'Make sure the file format is .snp or it has a similar inner structure.')
|
|
|
|
-
|
|
|
|
|
|
+ uploaded_file = st.file_uploader('Upload a file from your vector analizer. \
|
|
|
|
+ Make sure the file format is .snp or it has a similar inner structure.' )
|
|
|
|
+
|
|
# check .snp
|
|
# check .snp
|
|
data_format_snp = False
|
|
data_format_snp = False
|
|
if uploaded_file is None:
|
|
if uploaded_file is None:
|
|
- st.write("Demonstration: ")
|
|
|
|
|
|
+ st.write("DEMO: ")
|
|
# display DEMO
|
|
# display DEMO
|
|
data_format_snp = True
|
|
data_format_snp = True
|
|
try:
|
|
try:
|
|
@@ -269,13 +367,12 @@ def run(calc_function):
|
|
'''.split('\n')]
|
|
'''.split('\n')]
|
|
else:
|
|
else:
|
|
data = uploaded_file.readlines()
|
|
data = uploaded_file.readlines()
|
|
-
|
|
|
|
if uploaded_file.name[-4:-2]=='.s' and uploaded_file.name[-1]== 'p':
|
|
if uploaded_file.name[-4:-2]=='.s' and uploaded_file.name[-1]== 'p':
|
|
data_format_snp = True
|
|
data_format_snp = True
|
|
-
|
|
|
|
-
|
|
|
|
|
|
+
|
|
validator_status = '...'
|
|
validator_status = '...'
|
|
ace_preview_markers = []
|
|
ace_preview_markers = []
|
|
|
|
+ column_count = 0
|
|
|
|
|
|
# data loaded
|
|
# data loaded
|
|
circle_params = []
|
|
circle_params = []
|
|
@@ -283,32 +380,34 @@ def run(calc_function):
|
|
|
|
|
|
validator_status = read_data(data)
|
|
validator_status = read_data(data)
|
|
if validator_status == 'data read: success':
|
|
if validator_status == 'data read: success':
|
|
- hz, select_measurement_parameter, select_data_representation, input_ref_resistance=parse_heading(data)
|
|
|
|
-
|
|
|
|
- col1, col2 = st.columns(2)
|
|
|
|
- select_measurement_parameter = col1.selectbox('Measurement parameter',
|
|
|
|
- ['S', 'Z'],
|
|
|
|
- select_measurement_parameter)
|
|
|
|
- select_data_representation = col1.selectbox('Data representation',
|
|
|
|
- ['Frequency, real, imaginary',
|
|
|
|
- 'Frequency, magnitude, angle',
|
|
|
|
- 'Frequency, db, angle'],
|
|
|
|
- select_data_representation)
|
|
|
|
- if select_measurement_parameter=='Z':
|
|
|
|
- input_ref_resistance = col1.number_input(
|
|
|
|
- "Reference resistance:", min_value=0, value=input_ref_resistance)
|
|
|
|
- input_start_line = col1.number_input(
|
|
|
|
- "First line of data:", min_value=1, max_value=len(data))
|
|
|
|
- input_end_line = col1.number_input(
|
|
|
|
- "Last line of data:", min_value=1, max_value=len(data), value=len(data))
|
|
|
|
-
|
|
|
|
- f, r, i, validator_status = unpack_data(data, input_start_line, input_end_line)
|
|
|
|
- f = f * hz # to hz
|
|
|
|
|
|
+ hz, select_measurement_parameter, select_data_representation, input_ref_resistance = parse_heading(data)
|
|
|
|
+ col1, col2 = st.columns([1,2])
|
|
|
|
+
|
|
|
|
+ with col1.expander("Processing options"):
|
|
|
|
+ select_measurement_parameter = st.selectbox('Measurement parameter',
|
|
|
|
+ ['S', 'Z'],
|
|
|
|
+ select_measurement_parameter)
|
|
|
|
+ select_data_representation = st.selectbox('Data representation',
|
|
|
|
+ ['Frequency, real, imaginary',
|
|
|
|
+ 'Frequency, magnitude, angle',
|
|
|
|
+ 'Frequency, db, angle'],
|
|
|
|
+ select_data_representation)
|
|
|
|
+ if select_measurement_parameter=='Z':
|
|
|
|
+ input_ref_resistance = st.number_input(
|
|
|
|
+ "Reference resistance:", min_value=0, value=input_ref_resistance)
|
|
|
|
+ input_start_line = int(st.number_input(
|
|
|
|
+ "First line for processing:", min_value=1, max_value=len(data)))
|
|
|
|
+ input_end_line = int(st.number_input(
|
|
|
|
+ "Last line for processing:", min_value=1, max_value=len(data), value=len(data)))
|
|
|
|
+ data = data[input_start_line-1:input_end_line]
|
|
|
|
+
|
|
|
|
|
|
# Ace editor to show choosen data columns and rows
|
|
# Ace editor to show choosen data columns and rows
|
|
with col2.expander("File preview"):
|
|
with col2.expander("File preview"):
|
|
|
|
+ # st.button(copy selection)
|
|
|
|
+
|
|
# So little 'official' functionality in libs and lack of documentation
|
|
# So little 'official' functionality in libs and lack of documentation
|
|
- # therefore beware: css hacks
|
|
|
|
|
|
+ # therefore beware: css hacks
|
|
|
|
|
|
# yellow ~ ace_step
|
|
# yellow ~ ace_step
|
|
# light yellow ~ ace_highlight-marker
|
|
# light yellow ~ ace_highlight-marker
|
|
@@ -327,51 +426,108 @@ def run(calc_function):
|
|
|
|
|
|
# markers format:
|
|
# markers format:
|
|
#[{"startRow": 2,"startCol": 0,"endRow": 2,"endCol": 3,"className": "ace_error-marker","type": "text"}]
|
|
#[{"startRow": 2,"startCol": 0,"endRow": 2,"endCol": 3,"className": "ace_error-marker","type": "text"}]
|
|
-
|
|
|
|
- # add general marking for choosen data lines? better just mark the problematic lines?
|
|
|
|
- ace_preview_markers.append(
|
|
|
|
- {"startRow": input_start_line,"startCol": 0,
|
|
|
|
- "endRow": input_end_line+1,"endCol": 0,"className": "ace_highlight-marker","type": "text"})
|
|
|
|
|
|
|
|
- text_value = "Frequency,Hz | Re(S11) | Im(S11)\n" + \
|
|
|
|
- ''.join(data).strip()
|
|
|
|
- st_ace(value=text_value,
|
|
|
|
|
|
+ # add marking for choosen data lines TODO
|
|
|
|
+ ace_preview_markers.append({
|
|
|
|
+ "startRow": input_start_line - 1,
|
|
|
|
+ "startCol": 0,
|
|
|
|
+ "endRow": input_end_line,
|
|
|
|
+ "endCol": 0,
|
|
|
|
+ "className": "ace_highlight-marker",
|
|
|
|
+ "type": "text"
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ ace_text_value = ''.join(data).strip()
|
|
|
|
+ st_ace(value=ace_text_value,
|
|
readonly=True,
|
|
readonly=True,
|
|
auto_update=True,
|
|
auto_update=True,
|
|
- placeholder="Your data is empty",
|
|
|
|
|
|
+ placeholder="Your file is empty",
|
|
markers=ace_preview_markers,
|
|
markers=ace_preview_markers,
|
|
height="300px")
|
|
height="300px")
|
|
|
|
|
|
- st.write("Use range slider to choose best suitable data interval")
|
|
|
|
- plot_interact_abs_from_f(f, r, i)
|
|
|
|
|
|
+ column_count, validator_status = count_columns(data)
|
|
|
|
+
|
|
|
|
+ if validator_status == "data parsed":
|
|
|
|
+ input_ports_pair = 1
|
|
|
|
+ if column_count > 3:
|
|
|
|
+ input_ports_pair = st.number_input(
|
|
|
|
+ "Pair of data columns (pair of ports)\n with network parameters:",
|
|
|
|
+ min_value=1,
|
|
|
|
+ max_value=(column_count - 1) // 2,
|
|
|
|
+ value=1)
|
|
|
|
+ f, r, i, validator_status = unpack_data(
|
|
|
|
+ data,(input_ports_pair - 1) * 2 + 1, column_count, input_ref_resistance,
|
|
|
|
+ ace_preview_markers)
|
|
|
|
+ f = [x * hz for x in f] # to hz
|
|
|
|
+
|
|
|
|
+ st.write("Use range slider to choose best suitable data interval")
|
|
|
|
+ interval_range, interval_start, interval_end = plot_interact_abs_from_f(f, r, i, interval_range)
|
|
|
|
+
|
|
|
|
+ f_cut, r_cut, i_cut = [], [], []
|
|
|
|
+ if validator_status == "data parsed":
|
|
|
|
+ f_cut, r_cut, i_cut = (x[interval_start:interval_end]
|
|
|
|
+ for x in (f, r, i))
|
|
|
|
+
|
|
|
|
+ def copy_to_clip_s_single():
|
|
|
|
+ pyperclip.copy("# Hz S RI R 50\n" +
|
|
|
|
+ ''.join(f'{f_cut[x]} {r_cut[x]} {i_cut[x]}\n' for x in range(len(f_cut))))
|
|
|
|
+
|
|
|
|
+ st.button("Copy selected data interval to clipboard as .s1p", on_click=copy_to_clip_s_single)
|
|
|
|
+
|
|
|
|
+ if len(f_cut) < 3:
|
|
|
|
+ validator_status = "Choosen interval is too small, add more points"
|
|
|
|
|
|
- select_coupling_losses = st.checkbox(
|
|
|
|
- 'Apply corrections for coupling losses')
|
|
|
|
-
|
|
|
|
- if select_coupling_losses:
|
|
|
|
- st.write("Option: Lossy coupling")
|
|
|
|
- else:
|
|
|
|
- st.write("Option: Cable attenuation")
|
|
|
|
-
|
|
|
|
- f_cut, r_cut, i_cut = (x[interval_start:interval_end]
|
|
|
|
- for x in (f, r, i))
|
|
|
|
-
|
|
|
|
- if validator_status == 'data parsed':
|
|
|
|
- Q0, sigmaQ0, QL, sigmaQl, circle_params = calc_function(
|
|
|
|
- f_cut, r_cut, i_cut, select_coupling_losses)
|
|
|
|
|
|
+ st.write("Status: " + validator_status)
|
|
|
|
|
|
- out_precision = '0.7f'
|
|
|
|
- if Q0 <= 0 or QL <= 0:
|
|
|
|
- st.write("Negative Q detected, fitting may be inaccurate!")
|
|
|
|
|
|
+ if validator_status == "data parsed":
|
|
|
|
+ col1, col2 = st.columns(2)
|
|
|
|
+
|
|
|
|
+ check_coupling_loss = col1.checkbox(
|
|
|
|
+ 'Apply correction for coupling loss')
|
|
|
|
+
|
|
|
|
+ if check_coupling_loss:
|
|
|
|
+ col1.write("Option: Lossy coupling")
|
|
|
|
+ else:
|
|
|
|
+ col1.write("Option: Cable attenuation")
|
|
|
|
+
|
|
|
|
+ select_autoformat = col2.checkbox("Autoformat output", value=True)
|
|
|
|
+ precision = None
|
|
|
|
+ if not select_autoformat:
|
|
|
|
+ precision = col2.slider("Precision", min_value=0, max_value=7, value = 4)
|
|
|
|
+ precision = '0.'+str(precision)+'f'
|
|
|
|
+
|
|
|
|
+ Q0, sigmaQ0, QL, sigmaQL, circle_params = calc_function(
|
|
|
|
+ f_cut, r_cut, i_cut, check_coupling_loss)
|
|
|
|
+
|
|
|
|
+ if Q0 <= 0 or QL <= 0:
|
|
|
|
+ st.write("Negative Q detected, fitting may be inaccurate!")
|
|
|
|
+
|
|
|
|
+ if select_autoformat:
|
|
|
|
+ st.latex(
|
|
|
|
+ r'Q_0 =' +
|
|
|
|
+ f'{sigfig.round(Q0, uncertainty=sigmaQ0, style="PDG")}, '
|
|
|
|
+ + r'\;\;\varepsilon_{Q_0} =' +
|
|
|
|
+ f'{sigfig.round(sigmaQ0 / Q0, sigfigs=1, style="PDG")}')
|
|
|
|
+ st.latex(
|
|
|
|
+ r'Q_L =' +
|
|
|
|
+ f'{sigfig.round(QL, uncertainty=sigmaQL, style="PDG")}, '
|
|
|
|
+ + r'\;\;\varepsilon_{Q_L} =' +
|
|
|
|
+ f'{sigfig.round(sigmaQL / QL, sigfigs=1, style="PDG")}')
|
|
|
|
+ else:
|
|
|
|
+ st.latex(
|
|
|
|
+ r'Q_0 =' +
|
|
|
|
+ f'{format(Q0, precision)} \pm ' + f'{format(sigmaQ0, precision)}, '
|
|
|
|
+ + r'\;\;\varepsilon_{Q_0} =' +
|
|
|
|
+ f'{format(sigmaQ0 / Q0, precision)}')
|
|
|
|
+ st.latex(
|
|
|
|
+ r'Q_L =' +
|
|
|
|
+ f'{format(QL, precision)} \pm ' + f'{format(sigmaQL, precision)}, '
|
|
|
|
+ + r'\;\;\varepsilon_{Q_L} =' +
|
|
|
|
+ f'{format(sigmaQL / QL, precision)}')
|
|
|
|
|
|
- st.latex(r'Q_0 =' + f'{format(Q0, out_precision)} \pm {format(sigmaQ0, out_precision)}, ' +
|
|
|
|
- r'\;\;\varepsilon_{Q_0} =' + f'{format(sigmaQ0 / Q0, out_precision)}')
|
|
|
|
- st.latex(r'Q_L =' + f'{format(QL, out_precision)} \pm {format(sigmaQl, out_precision)}, ' +
|
|
|
|
- r'\;\;\varepsilon_{Q_L} =' + f'{format(sigmaQl / QL, out_precision)}')
|
|
|
|
|
|
+ with st.expander("Show static abs(S) plot"):
|
|
|
|
+ plot_abs_vs_f(f_cut, r_cut, i_cut)
|
|
|
|
|
|
- st.write("Status: " + validator_status)
|
|
|
|
|
|
+ select_show_excluded_points = st.checkbox("Show excluded points", value=True)
|
|
|
|
|
|
- if len(data) > 0 and validator_status == 'data parsed':
|
|
|
|
- with st.expander("Show static abs(S) plot"):
|
|
|
|
- plot_ref_from_f(f_cut, r_cut, i_cut)
|
|
|
|
- plot_smith(r, i, circle_params, r_cut, i_cut)
|
|
|
|
|
|
+ plot_smith(r, i, circle_params, r_cut, i_cut, select_show_excluded_points)
|