From 00021cceeac4d0054c76038c8d12ac8086f24811 Mon Sep 17 00:00:00 2001 From: NI-velmurugan Date: Tue, 27 Oct 2026 10:25:51 +0000 Subject: [PATCH 1/3] Adding Multi Record in NI-Scope --- CHANGELOG.md | 1 + build/templates/tox-system_tests.ini.mako | 3 + docs/niscope/examples.rst | 9 +++ generated/niscope/tox-system_tests.ini | 1 + src/niscope/examples/niscope_multi_record.py | 83 ++++++++++++++++++++ 5 files changed, 97 insertions(+) create mode 100644 src/niscope/examples/niscope_multi_record.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bc3af6804..afa19c6325 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1901,6 +1901,7 @@ #### [niscope] Unreleased - Added - (Common) Added `nitlsconfig[grpc]>=1.0.0a2` to the optional gRPC dependencies. + - Added the `niscope_multi_record.py` example. - Changed - Removed diff --git a/build/templates/tox-system_tests.ini.mako b/build/templates/tox-system_tests.ini.mako index c2b3d9131d..9c7f07cb3e 100644 --- a/build/templates/tox-system_tests.ini.mako +++ b/build/templates/tox-system_tests.ini.mako @@ -76,6 +76,9 @@ deps = % endif % if module_name == 'nidigital': ${module_name}-system_tests: nisync +% endif +% if module_name == 'niscope': + ${module_name}-system_tests: matplotlib; sys_platform != "win32" % endif ${module_name}-system_tests: pytest ${module_name}-system_tests: coverage diff --git a/docs/niscope/examples.rst b/docs/niscope/examples.rst index 71f84c469c..bb8fb969ad 100644 --- a/docs/niscope/examples.rst +++ b/docs/niscope/examples.rst @@ -30,6 +30,15 @@ niscope_fetch_into.py :encoding: utf8 :caption: `(niscope_fetch_into.py) `_ +niscope_multi_record.py +----------------------- + +.. literalinclude:: ../../src/niscope/examples/niscope_multi_record.py + :language: python + :linenos: + :encoding: utf8 + :caption: `(niscope_multi_record.py) `_ + niscope_read.py --------------- diff --git a/generated/niscope/tox-system_tests.ini b/generated/niscope/tox-system_tests.ini index 2f232d27ab..afadc34815 100644 --- a/generated/niscope/tox-system_tests.ini +++ b/generated/niscope/tox-system_tests.ini @@ -39,6 +39,7 @@ commands = deps = niscope-wheel_dep: build + niscope-system_tests: matplotlib; sys_platform != "win32" niscope-system_tests: pytest niscope-system_tests: coverage niscope-system_tests: numpy diff --git a/src/niscope/examples/niscope_multi_record.py b/src/niscope/examples/niscope_multi_record.py new file mode 100644 index 0000000000..88cda150de --- /dev/null +++ b/src/niscope/examples/niscope_multi_record.py @@ -0,0 +1,83 @@ +#!/usr/bin/python + +import argparse +import niscope +import pytest +import sys + +PLOT_STYLES = {'LINE': '-', 'POINTS': '.'} +PLOT_RELATIVE_TO = ('TRIGGER', 'ABSOLUTE') + + +def _plot_records(plt, waveforms, plot_style, plot_relative_to): + figure, axes = plt.subplots() + axes.set_title('Scope Multi Record') + axes.set_xlabel('Time relative to trigger (s)' if plot_relative_to == 'TRIGGER' else 'Absolute time (s)') + axes.set_ylabel('Amplitude (V)') + axes.grid(True) + for waveform in waveforms: + initial_x = waveform.relative_initial_x if plot_relative_to == 'TRIGGER' else waveform.absolute_initial_x + samples = waveform.samples.tolist() # samples is a memoryview, which matplotlib cannot plot + x = [initial_x + i * waveform.x_increment for i in range(len(samples))] + axes.plot(x, samples, PLOT_STYLES[plot_style], label=f'Record {waveform.record}') + axes.legend(loc='upper right') + return figure + + +def example(resource_name, options, channel, vertical_range, min_sample_rate, min_record_length, num_records, plot_style, plot_relative_to): + import matplotlib.pyplot as plt + + with niscope.Session(resource_name=resource_name, options=options) as session: + session.configure_vertical(range=vertical_range, coupling=niscope.VerticalCoupling.DC) + session.configure_horizontal_timing(min_sample_rate=min_sample_rate, min_num_pts=min_record_length, ref_position=50.0, num_records=num_records, enforce_realtime=True) + # The VI uses a fixed edge trigger on channel 0. + session.configure_trigger_edge(trigger_source='0', level=0.0, trigger_coupling=niscope.TriggerCoupling.DC, slope=niscope.TriggerSlope.POSITIVE) + with session.initiate(): + # Fetch waits for all records to be acquired and returns them all. + waveforms = session.channels[channel].fetch(num_records=num_records) + + figure = _plot_records(plt, waveforms, plot_style, plot_relative_to) + try: + if plt.fignum_exists(figure.number): + plt.show() + finally: + plt.close(figure) + return waveforms + + +def _main(argsv): + parser = argparse.ArgumentParser(description='Acquires multiple triggered records from a channel and plots them.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-n', '--resource-name', default='PXI1Slot2', help='Specifies the resource name of the device to initialize.') + parser.add_argument('-c', '--channel', default='0', help='Channel to use') + parser.add_argument('-v', '--vertical-range', default=5.0, type=float, help='Specifies the absolute value of the input range for a channel in volts.') + parser.add_argument('-r', '--min-sample-rate', default=1e7, type=float, help='The sampling rate for the acquisition.') + parser.add_argument('-N', '--num-records', default=2, type=int, help='The number of records to acquire') + parser.add_argument('-l', '--min-record-length', default=256, type=int, help='The minimum number of points you need in the record for each channel.') + parser.add_argument('-R', '--plot-relative-to', default='TRIGGER', choices=PLOT_RELATIVE_TO, type=str.upper, help='Time reference for the plot; ABSOLUTE requires a device that supports absolute timestamps.') + parser.add_argument('-p', '--plot-style', default='POINTS', choices=PLOT_STYLES.keys(), type=str.upper, help='Plot the records as a continuous line or as individual points.') + parser.add_argument('-op', '--option-string', default='', type=str, help='Specifies the initial value of certain properties for the session.') + args = parser.parse_args(argsv) + example(args.resource_name, args.option_string, args.channel, args.vertical_range, args.min_sample_rate, args.min_record_length, args.num_records, args.plot_style, args.plot_relative_to) + + +def main(): + _main(sys.argv[1:]) + + +def test_example(): + plt = pytest.importorskip('matplotlib.pyplot') + plt.switch_backend('Agg') # Avoid opening a chart window during the test + options = {'simulate': True, 'driver_setup': {'Model': '5164', 'BoardType': 'PXIe', }, } + waveforms = example('PXI1Slot2', options, '0', 5.0, 1e7, 256, 3, 'POINTS', 'TRIGGER') + assert len(waveforms) == 3 + + +def test_main(): + plt = pytest.importorskip('matplotlib.pyplot') + plt.switch_backend('Agg') # Avoid opening a chart window during the test + cmd_line = ['--option-string', 'Simulate=1, DriverSetup=Model:5164; BoardType:PXIe', '--num-records', '2', ] + _main(cmd_line) + + +if __name__ == '__main__': + main() From 3d2662cc05a5155a59b443f3065c80d8964240b6 Mon Sep 17 00:00:00 2001 From: NI-velmurugan Date: Tue, 27 Oct 2026 12:15:42 +0000 Subject: [PATCH 2/3] Adding additional parameter trigger type The additional trigger type since this example is independent of the trigger type and the LV as well documented that it is avoided to simplify the VI, but adding this will let the user aware that they can configure any type of trigger instead of just the edge trigger --- src/niscope/examples/niscope_multi_record.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/niscope/examples/niscope_multi_record.py b/src/niscope/examples/niscope_multi_record.py index 88cda150de..f6b1c466ac 100644 --- a/src/niscope/examples/niscope_multi_record.py +++ b/src/niscope/examples/niscope_multi_record.py @@ -7,6 +7,7 @@ PLOT_STYLES = {'LINE': '-', 'POINTS': '.'} PLOT_RELATIVE_TO = ('TRIGGER', 'ABSOLUTE') +TRIGGER_TYPES = ('EDGE', 'IMMEDIATE') def _plot_records(plt, waveforms, plot_style, plot_relative_to): @@ -24,14 +25,18 @@ def _plot_records(plt, waveforms, plot_style, plot_relative_to): return figure -def example(resource_name, options, channel, vertical_range, min_sample_rate, min_record_length, num_records, plot_style, plot_relative_to): +def example(resource_name, options, channel, vertical_range, min_sample_rate, min_record_length, num_records, plot_style, plot_relative_to, + trigger_type): import matplotlib.pyplot as plt with niscope.Session(resource_name=resource_name, options=options) as session: session.configure_vertical(range=vertical_range, coupling=niscope.VerticalCoupling.DC) session.configure_horizontal_timing(min_sample_rate=min_sample_rate, min_num_pts=min_record_length, ref_position=50.0, num_records=num_records, enforce_realtime=True) - # The VI uses a fixed edge trigger on channel 0. - session.configure_trigger_edge(trigger_source='0', level=0.0, trigger_coupling=niscope.TriggerCoupling.DC, slope=niscope.TriggerSlope.POSITIVE) + # Other trigger types (for example hysteresis or window) can be configured here; IMMEDIATE acquires without an input signal crossing the edge level. + if trigger_type == 'EDGE': + session.configure_trigger_edge(trigger_source='0', level=0.0, trigger_coupling=niscope.TriggerCoupling.DC, slope=niscope.TriggerSlope.POSITIVE) + else: + session.configure_trigger_immediate() with session.initiate(): # Fetch waits for all records to be acquired and returns them all. waveforms = session.channels[channel].fetch(num_records=num_records) @@ -49,6 +54,7 @@ def _main(argsv): parser = argparse.ArgumentParser(description='Acquires multiple triggered records from a channel and plots them.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('-n', '--resource-name', default='PXI1Slot2', help='Specifies the resource name of the device to initialize.') parser.add_argument('-c', '--channel', default='0', help='Channel to use') + parser.add_argument('-T', '--trigger-type', default='EDGE', choices=TRIGGER_TYPES, type=str.upper, help='EDGE configures the digitizer for edge triggering (channel 0, 0 V, DC coupling, rising slope). IMMEDIATE triggers as soon as the pretrigger samples are acquired.') parser.add_argument('-v', '--vertical-range', default=5.0, type=float, help='Specifies the absolute value of the input range for a channel in volts.') parser.add_argument('-r', '--min-sample-rate', default=1e7, type=float, help='The sampling rate for the acquisition.') parser.add_argument('-N', '--num-records', default=2, type=int, help='The number of records to acquire') @@ -57,7 +63,8 @@ def _main(argsv): parser.add_argument('-p', '--plot-style', default='POINTS', choices=PLOT_STYLES.keys(), type=str.upper, help='Plot the records as a continuous line or as individual points.') parser.add_argument('-op', '--option-string', default='', type=str, help='Specifies the initial value of certain properties for the session.') args = parser.parse_args(argsv) - example(args.resource_name, args.option_string, args.channel, args.vertical_range, args.min_sample_rate, args.min_record_length, args.num_records, args.plot_style, args.plot_relative_to) + example(args.resource_name, args.option_string, args.channel, args.vertical_range, args.min_sample_rate, args.min_record_length, args.num_records, args.plot_style, args.plot_relative_to, + args.trigger_type) def main(): @@ -68,7 +75,7 @@ def test_example(): plt = pytest.importorskip('matplotlib.pyplot') plt.switch_backend('Agg') # Avoid opening a chart window during the test options = {'simulate': True, 'driver_setup': {'Model': '5164', 'BoardType': 'PXIe', }, } - waveforms = example('PXI1Slot2', options, '0', 5.0, 1e7, 256, 3, 'POINTS', 'TRIGGER') + waveforms = example('PXI1Slot2', options, '0', 5.0, 1e7, 256, 3, 'POINTS', 'TRIGGER', 'EDGE') assert len(waveforms) == 3 From f9bc87d816022deee87dfebf5281ca7f31d0dafd Mon Sep 17 00:00:00 2001 From: NI-velmurugan Date: Thu, 29 Oct 2026 07:22:42 +0000 Subject: [PATCH 3/3] Rephrasing comments and changing the session config behaviour --- src/niscope/examples/niscope_multi_record.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/niscope/examples/niscope_multi_record.py b/src/niscope/examples/niscope_multi_record.py index f6b1c466ac..116dfd1d7d 100644 --- a/src/niscope/examples/niscope_multi_record.py +++ b/src/niscope/examples/niscope_multi_record.py @@ -30,15 +30,15 @@ def example(resource_name, options, channel, vertical_range, min_sample_rate, mi import matplotlib.pyplot as plt with niscope.Session(resource_name=resource_name, options=options) as session: - session.configure_vertical(range=vertical_range, coupling=niscope.VerticalCoupling.DC) + session.channels[channel].configure_vertical(range=vertical_range, coupling=niscope.VerticalCoupling.DC) session.configure_horizontal_timing(min_sample_rate=min_sample_rate, min_num_pts=min_record_length, ref_position=50.0, num_records=num_records, enforce_realtime=True) - # Other trigger types (for example hysteresis or window) can be configured here; IMMEDIATE acquires without an input signal crossing the edge level. + # Configure the selected trigger here; add other trigger types to this block as needed. if trigger_type == 'EDGE': session.configure_trigger_edge(trigger_source='0', level=0.0, trigger_coupling=niscope.TriggerCoupling.DC, slope=niscope.TriggerSlope.POSITIVE) else: session.configure_trigger_immediate() with session.initiate(): - # Fetch waits for all records to be acquired and returns them all. + # Wait for num_records to be acquired, then return their waveforms. waveforms = session.channels[channel].fetch(num_records=num_records) figure = _plot_records(plt, waveforms, plot_style, plot_relative_to)