123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377 |
- from streamlit_ace import st_ace
- from streamlit_echarts import st_echarts, JsCode
- import math
- import streamlit as st
- import matplotlib.pyplot as plt
- import numpy as np
- 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
- 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
- # 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
- # axis pointer values https://echarts.apache.org/en/option.html#axisPointer
- options = {
- "xAxis": {
- "type": "category",
- "data": f,
- "name": "Hz",
- "nameTextStyle": {"fontSize": 16},
- "axisLabel": {"fontSize": 16},
- },
- "yAxis": {
- "type": "value",
- "name": "abs(S)",
- "nameTextStyle": {"fontSize": 16},
- "axisLabel": {"fontSize": 16},
- # "axisPointer": {
- # "type": 'cross',
- # "label": {
- # "show":"true",
- # "formatter": JsCode(
- # "function(info){console.log(info);return 'line ' ;};"
- # ).js_code
- # }
- # }
- },
- "series": [{"data": abs_S, "type": "line", "name": "abs(S)"}],
- "height": 300,
- "dataZoom": [{"type": "slider", "start": 0, "end": 100, "height": 100, "bottom": 10}],
- "tooltip": {
- "trigger": "axis",
- "axisPointer": {
- "type": 'cross',
- # "label": {
- # "show":"true",
- # "formatter": JsCode(
- # "function(info){console.log(info);return 'line ' ;};"
- # ).js_code
- # }
- }
- },
- "toolbox": {
- "feature": {
- # "dataView": { "show": "true", "readOnly": "true" },
- "restore": {"show": "true"},
- }
- },
- }
- events = {
- "dataZoom": "function(params) { return [params.start, params.end] }",
- }
- # show echart with dataZoom and update intervals based on output
- interval_range = st_echarts(
- options=options, events=events, height="500px", key="render_basic_bar_events"
- )
- if interval_range is None:
- interval_range = (0, 100)
- n = len(f)
- interval_start, interval_end = (
- int(n*interval_range[id]*0.01) for id in (0, 1))
- # plot (abs(S))(f) chart with pyplot
- def plot_ref_from_f(f, r, i):
- fig = plt.figure(figsize=(10, 10))
- 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,
- max(f) + abs(max(f) - min(f)) * 0.1]
- ylim = [min(abs_S) - abs(max(abs_S) - min(abs_S)) * 0.5,
- max(abs_S) + abs(max(abs_S) - min(abs_S)) * 0.5]
- ax = fig.add_subplot()
- ax.set_xlim(xlim)
- ax.set_ylim(ylim)
- ax.grid(which='major', color='k', linewidth=1)
- ax.grid(which='minor', color='grey', linestyle=':', linewidth=0.5)
- plt.xlabel(r'$f,\; 1/c$', color='gray', fontsize=16, fontname="Cambria")
- plt.ylabel('$|S|$', color='gray', fontsize=16, fontname="Cambria")
- plt.title('Absolute value of reflection coefficient from frequency',
- fontsize=24, fontname="Cambria")
- ax.plot(f, abs_S, '+', ms=8, mew=2, color='#1946BA')
- st.pyplot(fig)
- def run(calc_function):
- def is_float(element) -> bool:
- try:
- float(element)
- val = float(element)
- if math.isnan(val) or math.isinf(val):
- raise ValueError
- return True
- except ValueError:
- return False
- # to utf-8
- def read_data(data):
- for x in range(len(data)):
- if type(data[x]) == bytes:
- try:
- data[x] = data[x].decode('utf-8-sig', 'ignore')
- except:
- return 'Not an utf-8-sig line №: ' + str(x)
- return 'data read: success'
- # for Touchstone .snp format
- def parse_heading(data):
- nonlocal data_format_snp
- if data_format_snp:
- for x in range(len(data)):
- if data[x][0]=='#':
- 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:]
- try:
- return hz_map[hz], para_map[measurement_parameter], repr_map[data_representation], ref_resistance
- except:
- break
- break
- return 1, 0, 0, 50
-
- def unpack_data(data, input_start_line, input_end_line):
- nonlocal select_measurement_parameter
- nonlocal select_data_representation
- 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
- 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)'
- break
- # 1: process according to data_placement
- a,b,c=[],[],[]
- 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':
- except:
- 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))):
- return_status = 'Wrong data type, expected number. Error on line: ' + str(x)
- break
- a,b,c=(float(x) for x in (a,b,c))
- f.append(a) # frequency
- # 2: process according to data_representation
- if select_data_representation == 'Frequency, real, imaginary':
- r.append(b) # Re
- i.append(c) # Im
- 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
- 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
- else:
- return_status = 'Wrong data format'
- break
- # 3: process according to measurement_parameter
- return f, r, i, return_status
- # make accessible specific range of numerical data choosen with interactive plot
- global interval_range, interval_start, interval_end
- # 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.')
-
- # check .snp
- data_format_snp = False
- if uploaded_file is None:
- st.write("Demonstration: ")
- # display DEMO
- data_format_snp = True
- try:
- with open('./resource/data/8_default_demo.s1p') as f:
- data = f.readlines()
- except:
- # 'streamlit run' call in the wrong directory. Display smaller demo:
- data =[line.strip()+'\n' for line in '''# Hz S MA R 50
- 11415403125 0.37010744 92.47802
- 11416090625 0.33831283 92.906929
- 11416778125 0.3069371 94.03318
- '''.split('\n')]
- else:
- data = uploaded_file.readlines()
- if uploaded_file.name[-4:-2]=='.s' and uploaded_file.name[-1]== 'p':
- data_format_snp = True
-
-
- validator_status = '...'
- ace_preview_markers = []
- # data loaded
- circle_params = []
- if len(data) > 0:
- validator_status = read_data(data)
- 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
- # Ace editor to show choosen data columns and rows
- with col2.expander("File preview"):
- # So little 'official' functionality in libs and lack of documentation
- # therefore beware: css hacks
- # yellow ~ ace_step
- # light yellow ~ ace_highlight-marker
- # green ~ ace_stack
- # red ~ ace_error-marker
- # no more good colors included in streamlit_ace for marking
- # st.markdown('''<style>
- # .choosen_option_1
- # {
- # color: rgb(49, 51, 63);
- # }</style>''', unsafe_allow_html=True)
- # markdown injection does not seems to work, since ace is in a different .html accessible via iframe
- # markers format:
- #[{"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,
- readonly=True,
- auto_update=True,
- placeholder="Your data is empty",
- markers=ace_preview_markers,
- height="300px")
- st.write("Use range slider to choose best suitable data interval")
- plot_interact_abs_from_f(f, r, i)
- 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)
- out_precision = '0.7f'
- if Q0 <= 0 or QL <= 0:
- st.write("Negative Q detected, fitting may be inaccurate!")
- 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)}')
- st.write("Status: " + validator_status)
- 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)
|