Skip to content

Commit d23d9c1

Browse files
Mike ProsserMike Prosser
authored andcommitted
Merge remote-tracking branch 'origin/main' into users/mprosser/input-improvements
2 parents de1a73b + 2f3724a commit d23d9c1

File tree

9 files changed

+718
-319
lines changed

9 files changed

+718
-319
lines changed

examples/nidaqmx/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ This is a nipanel example that displays an interactive Streamlit app and updates
1616

1717
### Usage
1818

19-
Run `poetry run examples/nidaqmx_continuous_analog_input/nidaqmx_continuous_analog_input.py`
19+
Run `poetry run examples/nidaqmx/nidaqmx_continuous_analog_input.py`
2020

examples/niscope/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Prerequisites
2+
===============
3+
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/digitizers, which have the module numbering pattern _51xx_. One example is NI PXIe-5114.
4+
## Sample
5+
6+
This is a nipanel example that displays an interactive Streamlit app and updates and fetches data from device.
7+
8+
### Feature
9+
10+
Script demonstrates NIScope waveform data getting continuously acquired.
11+
- Supports various data types
12+
13+
### Required Software
14+
15+
- Python 3.9 or later
16+
17+
### Usage
18+
19+
Run `poetry run examples/niscope/niscope_ex_fetch_forever.py`
20+
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Continuously acquires waveforms from NI-SCOPE, processes/scales them."""
2+
3+
import time
4+
from pathlib import Path
5+
from typing import Any
6+
7+
import hightime
8+
import niscope
9+
import numpy as np
10+
11+
import nipanel
12+
13+
panel_script_path = Path(__file__).with_name("niscope_ex_fetch_forever_panel.py")
14+
panel = nipanel.create_panel(panel_script_path)
15+
16+
print(f"Panel URL: {panel.panel_url}")
17+
18+
resource_name = "Dev1"
19+
channels = 0
20+
options = ""
21+
length = 1000
22+
samples_per_fetch = 1000
23+
24+
"""Example fetch data from device (Dev1)."""
25+
with niscope.Session(resource_name=resource_name, options=options) as session:
26+
session.configure_vertical(range=2, coupling=niscope.VerticalCoupling.DC, enabled=True)
27+
session.configure_horizontal_timing(
28+
min_sample_rate=100000000,
29+
min_num_pts=1000,
30+
ref_position=50.0,
31+
num_records=1000,
32+
enforce_realtime=True,
33+
)
34+
session.configure_trigger_software()
35+
36+
with session.initiate():
37+
wfm: np.ndarray[Any, np.dtype[np.int8]] = np.ndarray(
38+
length * samples_per_fetch, dtype=np.int8
39+
)
40+
waveforms = session.channels[channels].fetch_into(
41+
relative_to=niscope.FetchRelativeTo.READ_POINTER,
42+
offset=0,
43+
timeout=hightime.timedelta(seconds=5.0),
44+
waveform=wfm,
45+
)
46+
try:
47+
print(f"Press Ctrl + C to stop")
48+
while True:
49+
offset = session.meas_array_offset
50+
gain = session.meas_array_gain
51+
for i in range(len(waveforms)):
52+
time.sleep(0.2)
53+
amplitude_list = []
54+
total_data = waveforms[i].samples.tolist()
55+
for amplitude in total_data:
56+
amplitude = (amplitude * 10) * gain + offset
57+
amplitude_list.append(amplitude)
58+
panel.set_value("Waveform", amplitude_list)
59+
60+
except KeyboardInterrupt:
61+
pass
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Streamlit dashboard for visualizing NI-SCOPE waveform data in real time."""
2+
3+
import streamlit as st
4+
from streamlit_echarts import st_echarts
5+
6+
import nipanel
7+
8+
st.set_page_config(page_title="NI-SCOPE Example", page_icon="📈", layout="wide")
9+
st.title("NIScope EX Fetch Forever")
10+
11+
panel = nipanel.get_panel_accessor()
12+
13+
waveform = panel.get_value("Waveform", [0])
14+
15+
graph = {
16+
"animation": False,
17+
"tooltip": {"trigger": "axis"},
18+
"legend": {"data": ["Amplitude (V)"]},
19+
"xAxis": {
20+
"type": "category",
21+
"data": list(range(len(waveform))),
22+
"name": "Samples",
23+
"nameLocation": "center",
24+
"nameGap": 40,
25+
},
26+
"yAxis": {
27+
"type": "value",
28+
"name": "Amplitude(V)",
29+
"nameRotate": 90,
30+
"nameLocation": "center",
31+
"nameGap": 40,
32+
},
33+
"series": [
34+
{
35+
"name": "niscope data",
36+
"type": "line",
37+
"data": waveform,
38+
"emphasis": {"focus": "series"},
39+
"smooth": True,
40+
"seriesLayoutBy": "row",
41+
},
42+
],
43+
}
44+
st_echarts(options=graph, height="400px", width="75%", key="graph")
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Performance checker Example
2+
3+
This example measures the performance of a stremlit panel with a graph.
4+
5+
## Features
6+
7+
- Generates sine wave data with varying frequency
8+
- Displays the data in a graph
9+
- Updates rapidly
10+
- Shows timing information
11+
12+
### Required Software
13+
14+
- Python 3.9 or later
15+
16+
### Usage
17+
18+
Run `poetry run python examples/performance_checker/performance_checker.py`
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""Check the performance of get_value and set_value methods in nipanel."""
2+
3+
import math
4+
import time
5+
import timeit
6+
from pathlib import Path
7+
8+
import numpy as np
9+
10+
import nipanel
11+
12+
13+
panel_script_path = Path(__file__).with_name("performance_checker_panel.py")
14+
panel = nipanel.create_panel(panel_script_path)
15+
16+
amplitude = 1.0
17+
frequency = 1.0
18+
num_points = 1000
19+
time_points = np.linspace(0, num_points, num_points)
20+
sine_values = amplitude * np.sin(frequency * time_points)
21+
22+
23+
def _set_time_points() -> None:
24+
panel.set_value("time_points", time_points.tolist())
25+
26+
27+
def _set_amplitude() -> None:
28+
panel.set_value("amplitude", amplitude)
29+
30+
31+
def _get_time_points() -> None:
32+
panel.get_value("time_points", [0.0])
33+
34+
35+
def _get_amplitude() -> None:
36+
panel.get_value("amplitude", 1.0)
37+
38+
39+
def _get_unset_value() -> None:
40+
panel.get_value("unset_value", 1.0)
41+
42+
43+
iterations = 100
44+
45+
set_time_points_time = timeit.timeit(_set_time_points, number=iterations) * 1000 / iterations
46+
print(f"Average time to set 'time_points': {set_time_points_time:.2f} ms")
47+
48+
set_amplitude_time = timeit.timeit(_set_amplitude, number=iterations) * 1000 / iterations
49+
print(f"Average time to set 'amplitude': {set_amplitude_time:.2f} ms")
50+
51+
get_time_points_time = timeit.timeit(_get_time_points, number=iterations) * 1000 / iterations
52+
print(f"Average time to get 'time_points': {get_time_points_time:.2f} ms")
53+
54+
get_amplitude_time = timeit.timeit(_get_amplitude, number=iterations) * 1000 / iterations
55+
print(f"Average time to get 'amplitude': {get_amplitude_time:.2f} ms")
56+
57+
get_unset_value_time = timeit.timeit(_get_unset_value, number=iterations) * 1000 / iterations
58+
print(f"Average time to get 'unset_value': {get_unset_value_time:.2f} ms")
59+
60+
try:
61+
print(f"Panel URL: {panel.panel_url}")
62+
print("Press Ctrl+C to exit")
63+
64+
# Generate and update the sine wave data as fast as possible
65+
while True:
66+
time_points = np.linspace(0, num_points, num_points)
67+
sine_values = amplitude * np.sin(frequency * time_points)
68+
69+
panel.set_value("time_points", time_points.tolist())
70+
panel.set_value("sine_values", sine_values.tolist())
71+
panel.set_value("amplitude", amplitude)
72+
panel.set_value("frequency", frequency)
73+
74+
# Slowly vary the frequency for a more dynamic visualization
75+
frequency = 1.0 + 0.5 * math.sin(time.time() / 5.0)
76+
77+
except KeyboardInterrupt:
78+
print("Exiting...")
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""A Streamlit visualization panel for the performance_checker.py example script."""
2+
3+
import statistics
4+
import time
5+
import timeit
6+
from functools import partial
7+
from typing import Any, Tuple
8+
9+
import streamlit as st
10+
from streamlit_echarts import st_echarts
11+
12+
import nipanel
13+
14+
15+
def profile_get_value(
16+
panel: "nipanel.StreamlitPanelValueAccessor",
17+
value_id: str,
18+
default_value: Any = None,
19+
num_runs: int = 5,
20+
) -> Tuple[Any, float]:
21+
"""Measure the time it takes to get a value from the panel.
22+
23+
Args:
24+
panel: The panel accessor object
25+
value_id: The ID of the value to get
26+
default_value: Default value if the value is not found
27+
num_runs: Number of runs for timing
28+
29+
Returns:
30+
A tuple of (value, time_ms) where time_ms is the time in milliseconds
31+
"""
32+
value = panel.get_value(value_id, default_value)
33+
get_value_func = partial(panel.get_value, value_id, default_value)
34+
time_ms = timeit.timeit(get_value_func, number=num_runs) * 1000 / num_runs
35+
return value, time_ms
36+
37+
38+
st.set_page_config(page_title="Performance Checker Example", page_icon="📈", layout="wide")
39+
st.title("Performance Checker Example")
40+
41+
if "refresh_history" not in st.session_state:
42+
st.session_state.refresh_history = [] # List of tuples (timestamp, refresh_time_ms)
43+
44+
# Store current timestamp and calculate time since last refresh
45+
current_time = time.time()
46+
if "last_refresh_time" not in st.session_state:
47+
st.session_state.last_refresh_time = current_time
48+
time_since_last_refresh = 0.0
49+
else:
50+
time_since_last_refresh = (current_time - st.session_state.last_refresh_time) * 1000
51+
st.session_state.last_refresh_time = current_time
52+
53+
# Store refresh times with timestamps, keeping only the last 1 second of data
54+
st.session_state.refresh_history.append((current_time, time_since_last_refresh))
55+
56+
# Remove entries older than 1 second
57+
cutoff_time = current_time - 1.0 # 1 second ago
58+
st.session_state.refresh_history = [
59+
item for item in st.session_state.refresh_history if item[0] >= cutoff_time
60+
]
61+
62+
# Extract just the refresh times for calculations
63+
if st.session_state.refresh_history:
64+
refresh_history = [item[1] for item in st.session_state.refresh_history]
65+
else:
66+
refresh_history = []
67+
68+
min_refresh_time = min(refresh_history) if refresh_history else 0
69+
max_refresh_time = max(refresh_history) if refresh_history else 0
70+
avg_refresh_time = statistics.mean(refresh_history) if refresh_history else 0
71+
72+
panel = nipanel.get_panel_accessor()
73+
74+
num_timing_runs = 5
75+
time_points, time_points_ms = profile_get_value(panel, "time_points", [0.0], num_timing_runs)
76+
sine_values, sine_values_ms = profile_get_value(panel, "sine_values", [0.0], num_timing_runs)
77+
amplitude, amplitude_ms = profile_get_value(panel, "amplitude", 1.0, num_timing_runs)
78+
frequency, frequency_ms = profile_get_value(panel, "frequency", 1.0, num_timing_runs)
79+
unset_value, unset_value_ms = profile_get_value(panel, "unset_value", "default", num_timing_runs)
80+
81+
data = [{"value": [x, y]} for x, y in zip(time_points, sine_values)]
82+
83+
options = {
84+
"animation": False, # Disable animation for smoother updates
85+
"title": {"text": "Sine Wave"},
86+
"tooltip": {"trigger": "axis"},
87+
"xAxis": {"type": "value", "name": "Time (s)", "nameLocation": "middle", "nameGap": 30},
88+
"yAxis": {
89+
"type": "value",
90+
"name": "Amplitude",
91+
"nameLocation": "middle",
92+
"nameGap": 30,
93+
},
94+
"series": [
95+
{
96+
"data": data,
97+
"type": "line",
98+
"showSymbol": True,
99+
"smooth": True,
100+
"lineStyle": {"width": 2, "color": "#1f77b4"},
101+
"areaStyle": {"color": "#1f77b4", "opacity": 0.3},
102+
"name": "Sine Wave",
103+
}
104+
],
105+
}
106+
107+
st_echarts(options=options, height="400px", key="graph")
108+
109+
col1, col2, col3 = st.columns(3)
110+
with col1:
111+
st.metric("Amplitude", f"{amplitude:.2f}")
112+
st.metric("Frequency", f"{frequency:.2f} Hz")
113+
with col2:
114+
st.metric("Refresh Time", f"{time_since_last_refresh:.1f} ms")
115+
st.metric("Min Refresh Time", f"{min_refresh_time:.1f} ms")
116+
st.metric("Max Refresh Time", f"{max_refresh_time:.1f} ms")
117+
st.metric("Avg Refresh Time", f"{avg_refresh_time:.1f} ms")
118+
st.metric("FPS", f"{len(refresh_history)}")
119+
120+
with col3:
121+
st.metric("get time_points", f"{time_points_ms:.1f} ms")
122+
st.metric("get sine_values", f"{sine_values_ms:.1f} ms")
123+
st.metric("get amplitude", f"{amplitude_ms:.1f} ms")
124+
st.metric("get frequency", f"{frequency_ms:.1f} ms")
125+
st.metric("get unset_value", f"{unset_value_ms:.1f} ms")

0 commit comments

Comments
 (0)