123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374 |
- import streamlit as st
- import matplotlib.pyplot as plt
- import numpy as np
- import sigfig
- from streamlit_ace import st_ace
- from .draw_smith_utils import draw_smith_circle, plot_abs_s_gridlines, plot_im_z_gridlines, plot_re_z_gridlines
- from .show_amplitude_echart import plot_interact_abs_from_f
- from .data_parsing_utils import parse_snp_header, read_data, count_columns, prepare_snp, unpack_data
- def plot_smith(r, i, g, r_cut, i_cut):
- # maintaining state again (remember options for this session)
- if 'smith_options' not in st.session_state:
- st.session_state.smith_options = (True, True, False, False, False)
- with st.expander("Smith chart options"):
- smith_options_input = (st.checkbox(
- "Show excluded points",
- value=st.session_state.smith_options[0]),
- st.checkbox("Show grid",
- st.session_state.smith_options[1]),
- st.checkbox(
- "Show |S| gridlines",
- value=st.session_state.smith_options[2],
- ),
- st.checkbox(
- "Show Re(Z) gridlines",
- value=st.session_state.smith_options[3],
- ),
- st.checkbox(
- "Show Im(Z) gridlines",
- value=st.session_state.smith_options[4],
- ))
- if st.session_state.smith_options != smith_options_input:
- st.session_state.smith_options = smith_options_input
- st.experimental_rerun()
- (show_excluded_points, show_grid, show_Abs_S_gridlines,
- show_Re_Z_gridlines, show_Im_Z_gridlines) = st.session_state.smith_options
- fig = plt.figure(figsize=(10, 10))
- ax = fig.add_subplot()
- ax.axis('equal')
- minor_ticks = np.arange(-1.1, 1.1, 0.05)
- ax.set_xticks(minor_ticks, minor=True)
- 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(S)$', color='gray', fontsize=16, fontname="Cambria")
- plt.ylabel('$Im(S)$', color='gray', fontsize=16, fontname="Cambria")
- plt.title('Smith chart', fontsize=24, fontname="Cambria")
- # unit circle
- draw_smith_circle(ax, 0, 0, 1, '#1946BA')
- if not show_grid:
- ax.axis('off')
- if show_Abs_S_gridlines:
- # imshow is extremely slow, so draw it in place
- plot_abs_s_gridlines(ax)
- if show_Re_Z_gridlines:
- plot_re_z_gridlines(ax)
- if show_Im_Z_gridlines:
- plot_im_z_gridlines(ax)
- # input data points
- if show_excluded_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')
- # S-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
- draw_smith_circle(ax, x, y, radius, color='#FF8400')
- XLIM = [-1.3, 1.3]
- YLIM = [-1.3, 1.3]
- ax.set_xlim(XLIM)
- ax.set_ylim(YLIM)
- st.pyplot(fig)
- # plot abs(S) vs f chart with pyplot
- def plot_abs_vs_f(f, r, i, fitted_mag_s):
- fig = plt.figure(figsize=(10, 10))
- s = np.abs(np.array(r) + 1j * np.array(i))
- if st.session_state.legendselection == '|S| (dB)':
- m = np.min(np.where(s == 0, np.inf, s))
- s = list(20 * np.where(s == 0, np.log10(m), np.log10(s)))
- m = np.min(np.where(s == 0, np.inf, fitted_mag_s))
- fitted_mag_s = list(
- 20 * np.where(s == 0, np.log10(m), np.log10(fitted_mag_s)))
- s = list(s)
- min_f = min(f)
- max_f = max(f)
- xlim = [min_f - abs(max_f - min_f) * 0.1, max_f + abs(max_f - min_f) * 0.1]
- min_s = min(s)
- max_s = max(s)
- ylim = [min_s - abs(max_s - min_s) * 0.5, max_s + abs(max_s - min_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")
- if st.session_state.legendselection == '|S| (dB)':
- plt.ylabel('$|S|$ (dB)', color='gray', fontsize=16, fontname="Cambria")
- plt.title('|S| (dB) vs frequency', fontsize=24, fontname="Cambria")
- else:
- plt.ylabel('$|S|$', color='gray', fontsize=16, fontname="Cambria")
- plt.title('|S| vs frequency', fontsize=24, fontname="Cambria")
- ax.plot(f, s, '+', ms=8, mew=2, color='#1946BA')
- ax.plot(f, fitted_mag_s, '-', linewidth=3, color='#FF8400')
- st.pyplot(fig)
- def run(calc_function):
- # 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
- 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
- is_data_format_snp = False
- data_format_snp_number = 0
- if uploaded_file is None:
- st.write("DEMO: ")
- # display DEMO
- is_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 = [
- '# Hz S MA R 50\n\
- 11415403125 0.37010744 92.47802\n\
- 11416090625 0.33831283 92.906929\n\
- 11416778125 0.3069371 94.03318'
- ]
- else:
- data = uploaded_file.readlines()
- if uploaded_file.name[-4:-2] == '.s' and uploaded_file.name[-1] == 'p':
- is_data_format_snp = True
- data_format_snp_number = int(uploaded_file.name[-2])
- validator_status = '...'
- column_count = 0
- # data loaded
- circle_params = []
- if len(data) > 0:
- validator_status = read_data(data)
- if validator_status == 'data read, but not parsed':
- hz, select_measurement_parameter, select_data_representation, input_ref_resistance = parse_snp_header(
- data, is_data_format_snp)
- col1, col2 = st.columns([1, 2])
- ace_text_value = ''.join(data).strip()
- 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)
- if not is_data_format_snp:
- input_hz = st.selectbox('Unit of frequency',
- ['Hz', 'KHz', 'MHz', 'GHz'], 0)
- hz_map = {
- "ghz": 10**9,
- "mhz": 10**6,
- "khz": 10**3,
- "hz": 1
- }
- hz = hz_map[input_hz.lower()]
- 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
- 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 marking for choosen data lines?
- # todo or not todo?
- ace_preview_markers =[{
- "startRow": input_start_line - 1,
- "startCol": 0,
- "endRow": input_end_line,
- "endCol": 0,
- "className": "ace_highlight-marker",
- "type": "text"
- }]
- st_ace(value=ace_text_value,
- readonly=True,
- auto_update=True,
- placeholder="Your file is empty",
- markers=ace_preview_markers,
- height="300px")
- if is_data_format_snp and data_format_snp_number >= 3:
- data, validator_status = prepare_snp(data,
- data_format_snp_number)
- if validator_status == "data read, but not parsed":
- column_count, validator_status = count_columns(data)
- f, r, i = [], [], []
- if validator_status == "data parsed":
- input_ports_pair = 1
- if column_count > 3:
- pair_count = (column_count - 1) // 2
- input_ports_pair = st.number_input(
- "Choose pair of ports with network parameters:",
- min_value=1,
- max_value=pair_count,
- value=1)
- input_ports_pair_id = input_ports_pair - 1
- ports_count = round(pair_count**0.5)
- st.write('Choosen ports: ' + select_measurement_parameter +
- str(input_ports_pair_id // ports_count + 1) +
- str(input_ports_pair_id % ports_count + 1))
- f, r, i, validator_status = unpack_data(data,
- (input_ports_pair - 1) * 2 + 1,
- column_count,
- input_ref_resistance,
- select_measurement_parameter,
- select_data_representation)
- f = [x * hz for x in f] # to hz
- st.write("Use range slider to choose best suitable data interval")
- # make accessible a specific range of numerical data choosen with interactive plot
- # line id, line id
- interval_start, interval_end = plot_interact_abs_from_f(f,r,i)
- 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))
- with st.expander("Selected data interval as .s1p"):
- st_ace(value="# 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))),
- readonly=True,
- auto_update=True,
- placeholder="Selection is empty",
- height="150px")
- if len(f_cut) < 3:
- validator_status = "Choosen interval is too small, add more points"
- st.write("Status: " + validator_status)
- if validator_status == "data parsed":
- col1, col2 = st.columns(2)
- check_coupling_loss = col1.checkbox(
- 'Apply correction for coupling losses', value = False)
- if check_coupling_loss:
- col1.write("Option: Lossy coupling")
- else:
- col1.write("Option: Cable attenuation")
- select_autoformat = col2.checkbox("Autoformat output", value=True)
- precision = '0.0f'
- 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, k, ks, circle_params, fl, fitted_mag_s = 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!")
- def show_result_in_latex(name, value, uncertainty=None):
- nonlocal select_autoformat
- if uncertainty is not None:
- if select_autoformat:
- st.latex(
- name + ' =' +
- f'{sigfig.round(value, uncertainty=uncertainty, style="PDG")}, '
- + r'\;\;\varepsilon_{' + name + '} =' +
- f'{sigfig.round(uncertainty / value, sigfigs=1, style="PDG")}'
- )
- else:
- st.latex(name + ' =' + f'{format(value, precision)} \pm ' +
- f'{format(uncertainty, precision)}, ' +
- r'\;\;\varepsilon_{' + name + '} =' +
- f'{format(uncertainty / value, precision)}')
- else:
- if select_autoformat:
- st.latex(name + ' =' +
- f'{sigfig.round(value, sigfigs=5, style="PDG")}')
- else:
- st.latex(name + ' =' + f'{format(value, precision)}')
- show_result_in_latex('Q_0', Q0, sigmaQ0)
- show_result_in_latex('Q_L', QL, sigmaQL)
- show_result_in_latex(r'\kappa', k)
- if check_coupling_loss:
- show_result_in_latex(r'\kappa_s', ks)
- st.latex('f_L =' + f'{format(fl, precision)}' + r'\text{ }Hz')
- with st.expander("Show static abs(S) plot"):
- plot_abs_vs_f(f_cut, r_cut, i_cut, fitted_mag_s)
- plot_smith(r, i, circle_params, r_cut, i_cut)
|