diff --git a/examples/niscope/README.md b/examples/niscope/nicope_ex_fetch_forever/README.md similarity index 89% rename from examples/niscope/README.md rename to examples/niscope/nicope_ex_fetch_forever/README.md index 35b1e47d..88063569 100644 --- a/examples/niscope/README.md +++ b/examples/niscope/nicope_ex_fetch_forever/README.md @@ -19,5 +19,5 @@ Script demonstrates NIScope waveform data getting continuously acquired. ```pwsh poetry install --with examples -poetry run examples/niscope/niscope_ex_fetch_forever.py +poetry run examples\niscope\niscope_ex_fetch_forever\niscope_ex_fetch_forever.py ``` diff --git a/examples/niscope/niscope_ex_fetch_forever.py b/examples/niscope/nicope_ex_fetch_forever/niscope_ex_fetch_forever.py similarity index 100% rename from examples/niscope/niscope_ex_fetch_forever.py rename to examples/niscope/nicope_ex_fetch_forever/niscope_ex_fetch_forever.py diff --git a/examples/niscope/niscope_ex_fetch_forever_panel.py b/examples/niscope/nicope_ex_fetch_forever/niscope_ex_fetch_forever_panel.py similarity index 100% rename from examples/niscope/niscope_ex_fetch_forever_panel.py rename to examples/niscope/nicope_ex_fetch_forever/niscope_ex_fetch_forever_panel.py diff --git a/examples/niscope/niscope_binary_acquisition/README.md b/examples/niscope/niscope_binary_acquisition/README.md new file mode 100644 index 00000000..d704b9c9 --- /dev/null +++ b/examples/niscope/niscope_binary_acquisition/README.md @@ -0,0 +1,23 @@ +Prerequisites +=============== +Requires a Physical or Simulated Device. Refer to the [Getting Started Section](https://github.com/ni/nidaqmx-python/blob/master/README.rst) to learn how to create a simulated device. This example uses NI oscilloscopes and digitizers like the NI PXIe-5114 + +## Sample + +This is an nipanel example that displays an interactive Streamlit app and updates and fetches data from an NI device. + +### Feature + +Script demonstrates waveform data getting continuously acquired and being converted to binary data. +- Supports various data types + +### Required Software + +- Python 3.9 or later + +### Usage + +```pwsh +poetry install --with examples +poetry run examples\niscope\niscope_binary_acquisition\niscope_binary_acquisition.py +``` diff --git a/examples/niscope/niscope_binary_acquisition/niscope_binary_acquisition.py b/examples/niscope/niscope_binary_acquisition/niscope_binary_acquisition.py new file mode 100644 index 00000000..afe93686 --- /dev/null +++ b/examples/niscope/niscope_binary_acquisition/niscope_binary_acquisition.py @@ -0,0 +1,104 @@ +"""Continuously acquires waveforms from NI-SCOPE, processes/scales them.""" + +import time +from pathlib import Path +from typing import Any + +import hightime +import niscope +import numpy as np +from niscope.errors import Error + +import nipanel + +panel_script_path = Path(__file__).with_name("niscope_binary_acquisition_panel.py") +panel = nipanel.create_streamlit_panel(panel_script_path) + + +"""Example fetch data from device (Dev1).""" +panel.set_value("is_running", False) +panel.set_value("run_button", False) + +try: + panel.set_value("scope_error", "") + print(f"Panel URL: {panel.panel_url}") + print(f"Waiting for the 'Run' button to be pressed...") + print(f"(Press Ctrl + C to quit)") + while True: + panel.set_value("run_button", False) + while not panel.get_value("run_button", False): + time.sleep(0.1) + with niscope.Session(resource_name=panel.get_value("resource_name", "Dev1")) as session: + session.configure_vertical( + range=panel.get_value("vertical_range", 5.0), + coupling=niscope.VerticalCoupling.DC, + offset=panel.get_value("vertical_offset", 0.0), + ) + session.configure_horizontal_timing( + min_sample_rate=panel.get_value("min_sample_rate", 200000000.0), + min_num_pts=1000, + ref_position=50.0, + num_records=1000, + enforce_realtime=True, + ) + + session.configure_trigger_immediate() + + with session.initiate(): + data_size = panel.get_value("data_size", 8) + wfm: np.ndarray[Any, Any] + + if data_size == 8: + wfm = np.ndarray(1000 * 1000, dtype=np.int8) + + elif data_size == 16: + wfm = np.ndarray(1000 * 1000, dtype=np.int16) + else: + wfm = np.ndarray(1000 * 1000, dtype=np.int32) + + waveforms = session.channels[panel.get_value("channel_number", 0)].fetch_into( + relative_to=niscope.FetchRelativeTo.READ_POINTER, + offset=0, + timeout=hightime.timedelta(seconds=5.0), + waveform=wfm, + ) + + try: + panel.set_value("is_running", True) + + panel.set_value("stop_button", False) + while not panel.get_value("stop_button", False): + gain = 0 + offset = 0 + for waveform in waveforms: + gain = waveform.gain + offset = waveform.offset + panel.set_value("gain_factor", gain) + panel.set_value("offset", offset) + + for i in range(len(waveforms)): + if panel.get_value("stop_button", True): + break + else: + time.sleep(0.1) + binary_data = waveforms[i].samples.tolist() + panel.set_value("binary_data", waveforms[i].samples.tolist()) + + samples_array = np.array(binary_data) + voltage_values = samples_array * gain + offset + panel.set_value("scaled_voltage_data", voltage_values.tolist()) + + actual_binary_data_size = session.binary_sample_width + panel.set_value("actual_binary_data_size", actual_binary_data_size) + except KeyboardInterrupt: + pass + finally: + panel.set_value("is_running", False) + +except Error as e: + scope_error = str(e) + print(scope_error) + panel.set_value("scope_error", scope_error) + +except KeyboardInterrupt: + pass diff --git a/examples/niscope/niscope_binary_acquisition/niscope_binary_acquisition_panel.py b/examples/niscope/niscope_binary_acquisition/niscope_binary_acquisition_panel.py new file mode 100644 index 00000000..fa9157f3 --- /dev/null +++ b/examples/niscope/niscope_binary_acquisition/niscope_binary_acquisition_panel.py @@ -0,0 +1,192 @@ +"""Streamlit dashboard for visualizing NI-SCOPE waveform data in real time.""" + +import streamlit as st +from streamlit_echarts import st_echarts + +import nipanel + +st.set_page_config(page_title="NI-SCOPE Binary Acquisition", page_icon="📈", layout="wide") +st.title("NI-SCOPE Binary Acquisition") +panel = nipanel.get_streamlit_panel_accessor() + +left_col, right_col = st.columns(2) + + +st.markdown( + """ + + + """, + unsafe_allow_html=True, +) + + +with left_col: + with st.container(border=True): + if panel.get_value("is_running", False): + st.button(r"⏹️ Stop", key="stop_button") + elif not panel.get_value("is_running", False) and panel.get_value("scope_error", "") == "": + run_button = st.button(r"▶️ Run", key="run_button") + else: + st.error( + f"There was an error running the script. Fix the issue and re-run niscope_binary_acquisition.py \n\n {panel.get_value('scope_error', '')}" + ) + + st.text_input( + label="Resource Name", + value="Dev1", + disabled=panel.get_value("is_running", False), + key="resource_name", + ) + st.number_input( + "Channel", + value=0, + step=1, + disabled=panel.get_value("is_running", False), + key="channel_number", + ) + + st.title("Vertical") + st.number_input( + "Vertical Range(V)", + value=5.0, + step=1.0, + disabled=panel.get_value("is_running", False), + key="vertical_range", + ) + st.number_input( + "Vertical Offset", + value=0.0, + step=1.0, + disabled=panel.get_value("is_running", False), + key="vertical_offset", + ) + st.title("Horizontal") + st.number_input( + "Min Sample Rate", + value=20000000.0, + step=1.0, + disabled=panel.get_value("is_running", False), + key="min_sample_rate", + ) + st.number_input( + "Min Record Length", + value=1000, + step=1, + disabled=panel.get_value("is_running", False), + key="min_record_length", + ) + data_size = st.selectbox( + "Binary Data Size", + options=[8, 16, 32], + disabled=panel.get_value("is_running", False), + key="data_size", + ) + st.number_input( + "Actual Binary Data Size", + value=panel.get_value("actual_binary_data_size", 16), + step=1, + disabled=True, + key="actual_binary_data_size", + ) + st.title("Scaling Calculation") + st.number_input( + "Gain Factor", + value=panel.get_value("gain_factor", 0.0000), + step=0.0001, + disabled=True, + format="%.10f", + key="gain_factor", + ) + st.number_input( + "Offset", + value=panel.get_value("offset", 0.0000), + step=0.0001, + disabled=True, + key="offset", + ) + + +with right_col: + with st.container(border=True): + st.title("Binary Waveform Graph") + binary_data = panel.get_value("binary_data", [0]) + + binary_graph = { + "animation": False, + "tooltip": {"trigger": "axis"}, + "legend": {"data": ["Amplitude (ADC Codes)"]}, + "xAxis": { + "type": "category", + "data": list(range(len(binary_data))), + "name": "Samples", + "nameLocation": "center", + "nameGap": 40, + }, + "yAxis": { + "type": "value", + "name": "Amplitude(ADC Codes)", + "nameRotate": 90, + "nameLocation": "center", + "nameGap": 40, + }, + "series": [ + { + "name": "niscope data", + "type": "line", + "data": binary_data, + "emphasis": {"focus": "series"}, + "smooth": True, + "seriesLayoutBy": "row", + }, + ], + } + st_echarts(options=binary_graph, height="400px", width="75%", key="binary_graph") + with st.container(border=True): + st.title("Scaled Voltage Graph") + scaled_voltage_data = panel.get_value("scaled_voltage_data", [0]) + scaled_voltage_graph = { + "animation": False, + "tooltip": {"trigger": "axis"}, + "legend": {"data": ["Amplitude (V)"]}, + "xAxis": { + "type": "category", + "data": list(range(len(scaled_voltage_data))), + "name": "Samples", + "nameLocation": "center", + "nameGap": 40, + }, + "yAxis": { + "type": "value", + "name": "Amplitude(V)", + "nameRotate": 90, + "nameLocation": "center", + "nameGap": 40, + }, + "series": [ + { + "name": "niscope data", + "type": "line", + "data": scaled_voltage_data, + "emphasis": {"focus": "series"}, + "smooth": True, + "seriesLayoutBy": "row", + }, + ], + } + st_echarts( + options=scaled_voltage_graph, height="400px", width="75%", key="scaled_voltage_graph" + ) diff --git a/examples/niscope/niscope_configured_acquisition/README.md b/examples/niscope/niscope_configured_acquisition/README.md new file mode 100644 index 00000000..be24498c --- /dev/null +++ b/examples/niscope/niscope_configured_acquisition/README.md @@ -0,0 +1,23 @@ +Prerequisites +=============== +Requires a Physical or Simulated Device. Refer to the [Getting Started Section](https://github.com/ni/nidaqmx-python/blob/master/README.rst) to learn how to create a simulated device. This example uses NI oscilloscopes and digitizers like the NI PXIe-5114 + +## Sample + +This is an nipanel example that displays an interactive Streamlit app and updates and fetches data from an NI device. + +### Feature + +Script demonstrates waveform data getting continuously acquired and being converted to binary data. +- Supports various data types + +### Required Software + +- Python 3.9 or later + +### Usage + +```pwsh +poetry install --with examples +poetry run examples\niscope\niscope_configured_acquisition\niscope_configured_acquisition.py +``` diff --git a/examples/niscope/niscope_configured_acquisition/niscope_configured_acquisition.py b/examples/niscope/niscope_configured_acquisition/niscope_configured_acquisition.py new file mode 100644 index 00000000..d0144a98 --- /dev/null +++ b/examples/niscope/niscope_configured_acquisition/niscope_configured_acquisition.py @@ -0,0 +1,119 @@ +"""Continuously acquires waveforms from NI-SCOPE, processes/scales them.""" + +import time +from pathlib import Path + +import hightime +import niscope +import numpy as np +from niscope.errors import Error + +import nipanel + +panel_script_path = Path(__file__).with_name("niscope_configured_acquisition_panel.py") +panel = nipanel.create_streamlit_panel(panel_script_path) + +panel.set_value("is_running", False) +panel.set_value("run_button", False) + +try: + panel.set_value("scope_error", "") + print(f"Panel URL: {panel.panel_url}") + print(f"Waiting for the 'Run' button to be pressed...") + print(f"(Press Ctrl + C to quit)") + while True: + panel.set_value("run_button", False) + while not panel.get_value("run_button", False): + time.sleep(0.1) + with niscope.Session(resource_name=panel.get_value("resource_name", "Dev1")) as session: + session.configure_vertical( + range=panel.get_value("vertical_range", 5.0), + coupling=niscope.VerticalCoupling.DC, + offset=panel.get_value("vertical_offset", 0.0), + ) + session.configure_chan_characteristics( + input_impedance=panel.get_value("input_impedance", 1.0e6), + max_input_frequency=panel.get_value("max_input_frequency", 100.0e6), + ) + session.configure_horizontal_timing( + min_sample_rate=panel.get_value("min_sample_rate", 10.0e6), + min_num_pts=panel.get_value("min_record_length", 1000), + ref_position=panel.get_value("ref_position", 0.0), + num_records=1000, + enforce_realtime=panel.get_value("enforce_realtime", True), + ) + trigger_type = int(panel.get_value("trigger_type", "1")) + if trigger_type == 1: + immediate = session.configure_trigger_immediate() + elif trigger_type == 2: + edge = session.configure_trigger_edge( + trigger_source=str(panel.get_value("edge_source", 0)), + level=panel.get_value("edge_level", 0.0), + slope=panel.get_value("edge_slope", niscope.TriggerSlope.POSITIVE), + trigger_coupling=panel.get_value("edge_coupling", niscope.TriggerCoupling.DC), + delay=hightime.timedelta(seconds=panel.get_value("digital_delay", 0.0)), + ) + elif trigger_type == 3: + digital = session.configure_trigger_digital( + trigger_source=panel.get_value("digital_source", "PFI 1"), + slope=panel.get_value("digital_slope", niscope.TriggerSlope.POSITIVE), + delay=hightime.timedelta(seconds=panel.get_value("digital_delay", 0.0)), + ) + elif trigger_type == 4: + window = session.configure_trigger_window( + trigger_source=str(panel.get_value("window_source", 0)), + window_mode=panel.get_value("window_mode", niscope.TriggerWindowMode.ENTERING), + low_level=panel.get_value("window_low_level", -0.1), + high_level=panel.get_value("window_high_level", 0.1), + trigger_coupling=panel.get_value("window_coupling", niscope.TriggerCoupling.DC), + delay=hightime.timedelta(seconds=panel.get_value("digital_delay", 0.0)), + ) + else: + hysteresis = session.configure_trigger_hysteresis( + trigger_source=str(panel.get_value("hysteresis_source", 0)), + level=panel.get_value("hysteresis_level", 0.0), + hysteresis=panel.get_value("hysteresis", 0.05), + slope=panel.get_value("hysteresis_slope", niscope.TriggerSlope.POSITIVE), + trigger_coupling=panel.get_value( + "hysteresis_coupling", niscope.TriggerCoupling.DC + ), + delay=hightime.timedelta(seconds=panel.get_value("digital_delay", 0.0)), + ) + + with session.initiate(): + wfm = np.array( + session.channels[panel.get_value("channel_number", 0)].fetch( + num_samples=1000, + num_records=1000, + relative_to=niscope.FetchRelativeTo.READ_POINTER, + offset=0, + timeout=hightime.timedelta(seconds=panel.get_value("timeout", 5.0)), + ) + ) + try: + panel.set_value("is_running", True) + + panel.set_value("stop_button", False) + while not panel.get_value("stop_button", False): + + for i in range(len(wfm)): + if panel.get_value("stop_button", True): + break + else: + data = wfm[i].samples.tolist() + panel.set_value("waveform_data", data) + panel.set_value("actual_record_length", session.horz_record_length) + panel.set_value("actual_sample_rate", session.samp_clk_timebase_rate) + panel.set_value("auto_triggered", session.trigger_auto_triggered) + except KeyboardInterrupt: + pass + finally: + panel.set_value("is_running", False) + +except Error as e: + scope_error = str(e) + print(scope_error) + panel.set_value("scope_error", scope_error) + +except KeyboardInterrupt: + pass diff --git a/examples/niscope/niscope_configured_acquisition/niscope_configured_acquisition_panel.py b/examples/niscope/niscope_configured_acquisition/niscope_configured_acquisition_panel.py new file mode 100644 index 00000000..ee583750 --- /dev/null +++ b/examples/niscope/niscope_configured_acquisition/niscope_configured_acquisition_panel.py @@ -0,0 +1,345 @@ +"""Streamlit dashboard for visualizing NI-SCOPE waveform data in real time.""" + +import extra_streamlit_components as stx # type: ignore[import-untyped] +import niscope +import streamlit as st +from streamlit_echarts import st_echarts + +import nipanel +from nipanel.controls import enum_selectbox + +st.set_page_config(page_title="NI-SCOPE EX Configured Acquisition", page_icon="📈", layout="wide") +st.title("NI-SCOPE EX Configured Acquisition") +panel = nipanel.get_streamlit_panel_accessor() + +left_col, right_col = st.columns(2) + + +st.markdown( + """ + + + """, + unsafe_allow_html=True, +) + +with left_col: + with st.container(border=True): + if panel.get_value("is_running", False): + st.button(r"⏹️ Stop", key="stop_button") + elif not panel.get_value("is_running", False) and panel.get_value("scope_error", "") == "": + run_button = st.button(r"▶️ Run", key="run_button") + else: + st.error( + f"There was an error running the script. Fix the issue and re-run niscope_binary_acquisition.py \n\n {panel.get_value('scope_error', '')}" + ) + + st.text_input( + label="Resource Name", + value="Dev1", + disabled=panel.get_value("is_running", False), + key="resource_name", + ) + st.number_input( + "Channel", + value=0, + step=1, + disabled=panel.get_value("is_running", False), + key="channel_number", + ) + st.number_input( + "Timeout(s)", + value=5.00, + step=1.00, + disabled=panel.get_value("is_running", False), + key="timeout", + ) + + st.title("Vertical") + st.number_input( + "Vertical Range(V)", + value=5.0, + step=1.0, + disabled=panel.get_value("is_running", False), + key="vertical_range", + ) + st.number_input( + "Probe Attenuation", + value=1.0, + step=1.0, + disabled=panel.get_value("is_running", False), + key="probe_attenuation", + ) + st.number_input( + "Vertical Offset", + value=0.0, + step=1.0, + disabled=panel.get_value("is_running", False), + key="vertical_offset", + ) + enum_selectbox( + panel, + label="Vertical Coupling", + value=niscope.VerticalCoupling.DC, + disabled=panel.get_value("is_running", False), + key="vertical_coupling", + ) + + st.title("Channel") + st.number_input( + "Max. Input Frequency(Hz)", + value=0.0, + step=1.0, + disabled=panel.get_value("is_running", False), + key="max_input_frequency", + ) + st.number_input( + "Input Impedance", + value=1.0e6, + step=1000.0, + disabled=panel.get_value("is_running", False), + key="input_impedance", + ) + + st.title("Horizontal") + st.radio( + "Enforce Realtime", + options=[True, False], + disabled=panel.get_value("is_running", False), + key="enforce_realtime", + ) + + sample_rate = st.number_input( + "Min Sample Rate", + value=10000000.0, + step=1.0, + disabled=panel.get_value("is_running", False), + key="min_sample_rate", + ) + st.number_input( + "Actual Sample Rate", + value=panel.get_value("actual_sample_rate", 1.0e7), + step=1.0, + disabled=True, + key="actual_sample_rate", + ) + record_length = st.number_input( + "Min Record Length", + value=1000, + step=1, + disabled=panel.get_value("is_running", False), + key="min_record_length", + ) + st.number_input( + "Actual Record Length", + value=panel.get_value("actual_record_length", 1000), + step=1, + disabled=True, + key="actual_record_length", + ) + + +with right_col: + with st.container(border=True): + st.title("Waveform Graph") + waveform_data = panel.get_value("waveform_data", [0.0]) + waveform_graph = { + "animation": False, + "tooltip": {"trigger": "axis"}, + "legend": {"data": ["Amplitude (V)"]}, + "xAxis": { + "type": "category", + "data": [x / record_length for x in range(len(waveform_data))], + "name": "Time (s)", + "nameLocation": "center", + "nameGap": 40, + }, + "yAxis": { + "type": "value", + "name": "Amplitude(V)", + "nameRotate": 90, + "nameLocation": "center", + "nameGap": 40, + }, + "series": [ + { + "name": "niscope data", + "type": "line", + "data": waveform_data, + "emphasis": {"focus": "series"}, + "smooth": True, + "seriesLayoutBy": "row", + }, + ], + } + st_echarts(options=waveform_graph, height="400px", width="75%", key="waveform_graph") + st.title("Trigger") + trigger_type = stx.tab_bar( + data=[ + stx.TabBarItemData(id=1, title="Immediate", description=""), + stx.TabBarItemData(id=2, title="Edge", description=""), + stx.TabBarItemData(id=3, title="Digital", description=""), + stx.TabBarItemData(id=4, title="Window", description=""), + stx.TabBarItemData(id=5, title="Hysteresis", description=""), + ], + default=1, + ) + panel.set_value("trigger_type", trigger_type) + + if trigger_type == "1": + with st.container(border=True): + st.write( + "Note: When configured for Immediate reference trigger, this example does not configure any additional trigger settings - the acquisition is triggered as soon as possible after being initiated by driver software." + ) + if trigger_type == "2": + with st.container(border=True): + st.number_input( + "Trigger Source", + value=0, + step=1, + disabled=panel.get_value("is_running", False), + key="edge_source", + ) + st.number_input( + "Trigger Level", + value=0.0, + step=1.0, + disabled=panel.get_value("is_running", False), + key="edge_level", + ) + enum_selectbox( + panel, + label="Trigger Slope", + value=niscope.TriggerSlope.POSITIVE, + disabled=panel.get_value("is_running", False), + key="edge_slope", + ) + enum_selectbox( + panel, + label="Trigger Coupling", + value=niscope.TriggerCoupling.DC, + disabled=panel.get_value("is_running", False), + key="edge_coupling", + ) + if trigger_type == "3": + with st.container(border=True): + st.text_input( + "Trigger Source", + value="PFI 1", + disabled=panel.get_value("is_running", False), + key="digital_source", + ) + enum_selectbox( + panel, + label="Trigger Slope", + value=niscope.TriggerSlope.POSITIVE, + disabled=panel.get_value("is_running", False), + key="digital_slope", + ) + if trigger_type == "4": + with st.container(border=True): + st.number_input( + "Trigger Source", + value=0, + step=1, + disabled=panel.get_value("is_running", False), + key="window_source", + ) + enum_selectbox( + panel, + label="Window Mode", + value=niscope.TriggerWindowMode.ENTERING, + disabled=panel.get_value("is_running", False), + key="window_mode", + ) + st.number_input( + "Window Low Level", + value=-0.1, + step=1.0, + disabled=panel.get_value("is_running", False), + key="window_low_level", + ) + st.number_input( + "Window High Level", + value=0.1, + step=1.0, + disabled=panel.get_value("is_running", False), + key="window_high_level", + ) + enum_selectbox( + panel, + label="Trigger Coupling", + value=niscope.TriggerCoupling.DC, + disabled=panel.get_value("is_running", False), + key="window_coupling", + ) + + if trigger_type == "5": + with st.container(border=True): + st.number_input( + "Trigger Source", + value=0, + step=1, + disabled=panel.get_value("is_running", False), + key="hysteresis_source", + ) + st.number_input( + "Trigger Level", + value=0.0, + step=1.0, + disabled=panel.get_value("is_running", False), + key="hysteresis_trigger_level", + ) + st.number_input( + "Hysteresis", + value=0.05, + step=1.0, + disabled=panel.get_value("is_running", False), + key="hysteresis", + ) + enum_selectbox( + panel, + label="Trigger Slope", + value=niscope.TriggerSlope.POSITIVE, + disabled=panel.get_value("is_running", False), + key="hysteresis_slope", + ) + enum_selectbox( + panel, + label="Trigger Coupling", + value=niscope.TriggerCoupling.DC, + disabled=panel.get_value("is_running", False), + key="hysteresis_coupling", + ) + st.title("Common Trigger Settings") + st.number_input( + "Trigger Delay (s)", + value=0.0, + step=0.1, + disabled=panel.get_value("is_running", False), + key="trigger_delay", + ) + st.number_input( + "Ref Position", + value=50.0, + step=1.0, + disabled=panel.get_value("is_running", False), + key="ref_position", + ) + if panel.get_value("auto_triggered", False): + st.write("This acquisition is auto-triggered.") + else: + st.write("This acquisition is not auto-triggered.")