From 21ef75156de32cf06933d64789b75e516d4d9e66 Mon Sep 17 00:00:00 2001 From: NI-velmurugan Date: Fri, 23 Oct 2026 13:17:10 +0000 Subject: [PATCH 1/4] Rebasing Master --- CHANGELOG.md | 2 + build/templates/tox-system_tests.ini.mako | 3 + docs/niscope/examples.rst | 18 +++ generated/niscope/tox-system_tests.ini | 1 + .../examples/niscope_external_clocking.py | 105 ++++++++++++++++++ 5 files changed, 129 insertions(+) create mode 100644 src/niscope/examples/niscope_external_clocking.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3556fcb1b7..08ff814d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1899,6 +1899,8 @@ #### [niscope] Unreleased - Added - (Common) Added `nitlsconfig[grpc]>=1.0.0a2` to the optional gRPC dependencies. + - Added the `niscope_digital_filtering.py` example. + - Added the `niscope_external_clocking.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..25f1c497da 100644 --- a/docs/niscope/examples.rst +++ b/docs/niscope/examples.rst @@ -3,6 +3,24 @@ Examples `You can download all niscope examples for latest version here `_ +niscope_digital_filtering.py +---------------------------- + +.. literalinclude:: ../../src/niscope/examples/niscope_digital_filtering.py + :language: python + :linenos: + :encoding: utf8 + :caption: `(niscope_digital_filtering.py) `_ + +niscope_external_clocking.py +---------------------------- + +.. literalinclude:: ../../src/niscope/examples/niscope_external_clocking.py + :language: python + :linenos: + :encoding: utf8 + :caption: `(niscope_external_clocking.py) `_ + niscope_fetch.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_external_clocking.py b/src/niscope/examples/niscope_external_clocking.py new file mode 100644 index 0000000000..f220463445 --- /dev/null +++ b/src/niscope/examples/niscope_external_clocking.py @@ -0,0 +1,105 @@ +#!/usr/bin/python + +import argparse +import niscope +import pytest +import sys + + +def _create_chart(plt): + figure, axes = plt.subplots() + axes.set_title('Waveform') + axes.set_xlabel('Time (s)') + axes.set_ylabel('Voltage (V)') + axes.grid(True) + figure.tight_layout() + return figure, axes + + +def _update_chart(axes, waveforms, actual_sample_rate): + # One line per channel is created on the first acquisition and updated afterwards + if len(axes.lines) < len(waveforms): + while len(axes.lines) < len(waveforms): + axes.plot([], [], label=waveforms[len(axes.lines)].channel) + axes.legend(loc='upper right') + for line, waveform in zip(axes.lines, waveforms): + x = [waveform.relative_initial_x + i * waveform.x_increment for i in range(len(waveform.samples))] + # read() returns samples as a memoryview, which matplotlib cannot copy + line.set_data(x, list(waveform.samples)) + axes.set_title(f'Waveform (Actual Sample Rate: {actual_sample_rate:g} S/s)') + axes.relim() + axes.autoscale_view() + + +def example(resource_name, channels, options, min_record_length, range, offset, source, rate, divisor, multiplier, total_iterations): + # Imported lazily because matplotlib is unavailable on some platforms (e.g. 32-bit Python) + import matplotlib.pyplot as plt + + with niscope.Session(resource_name=resource_name, options=options) as session: + channel_session = session.channels[channels] + channel_session.configure_vertical(range=range, coupling=niscope.VerticalCoupling.DC, offset=offset) + session.configure_trigger_immediate() + session.horz_min_num_pts = min_record_length + session.samp_clk_timebase_src = source + session.samp_clk_timebase_rate = rate + session.samp_clk_timebase_div = divisor + session.sample_clock_timebase_multiplier = multiplier + + figure, axes = _create_chart(plt) + + iterations = 0 + try: + # A total_iterations of None runs until the chart window is closed or Ctrl+C is pressed + while plt.fignum_exists(figure.number) and (total_iterations is None or iterations < total_iterations): + # read() initiates the acquisition, waits for it to complete and fetches the data + waveforms = channel_session.read(num_samples=min_record_length) + _update_chart(axes, waveforms, session.horz_sample_rate) + iterations += 1 + plt.pause(0.01) + + # The acquisition has stopped; keep the final chart on screen until its window is closed + if total_iterations is not None and plt.fignum_exists(figure.number): + plt.show() + except KeyboardInterrupt: + pass + finally: + plt.close(figure) + + +def _main(argsv): + parser = argparse.ArgumentParser(description='Continuously acquires and plots a waveform using an external sample clock.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('-n', '--resource-name', default='PXI1Slot3', help='Specifies the resource name of the device to initialize.') + parser.add_argument('-c', '--channels', default='0', help='Comma-separated list of channels to plot, one line per channel') + parser.add_argument('-v', '--range', default=1.0, type=float, help='Specifies the absolute value of the input range for a channel in volts.') + parser.add_argument('--offset', default=0.0, type=float, help='Specifies the location of the center of the range. The value is with respect to ground and is in volts.') + parser.add_argument('-l', '--min-record-length', default=1000, type=int, help='Specifies the minimum number of points you require in the waveform record for each channel.') + parser.add_argument('-s', '--source', default='VAL_CLK_IN', type=str, help='Specifies the source of the sample clock timebase, which is the timebase used to control waveform sampling.') + parser.add_argument('-r', '--rate', default=1e8, type=float, help='If samp_clk_timebase_src is an external source, specifies the frequency in hertz of the external clock used as the timebase source.') + parser.add_argument('-d', '--divisor', default=1, type=int, help='If samp_clk_timebase_src is an external source, specifies the ratio between the sample clock timebase rate and the actual sample rate, which can be slower.') + parser.add_argument('-m', '--multiplier', default=1, type=int, help='If samp_clk_timebase_src is an external source, this property specifies the ratio between the samp_clk_timebase_rate and the actual sample rate, which can be higher.') + parser.add_argument('--total-iterations', default=None, type=int, help='Stops after this many acquisitions and keeps the chart open until its window is closed; omit to run until the chart window is closed or Ctrl+C is pressed.') + 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.channels, args.option_string, args.min_record_length, args.range, args.offset, args.source, args.rate, args.divisor, args.multiplier, args.total_iterations) + + +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', }, } + example('PXI1Slot2', '0', options, 1000, 1.0, 0.0, 'VAL_CLK_IN', 1e8, 1, 1, 2) + + +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', '--total-iterations', '2', ] + _main(cmd_line) + + +if __name__ == '__main__': + main() From 0eb83541343b947c54761ccf36780ccfb985f3e6 Mon Sep 17 00:00:00 2001 From: NI-velmurugan Date: Fri, 23 Oct 2026 12:23:37 +0000 Subject: [PATCH 2/4] Merging with Master --- CHANGELOG.md | 1 - docs/niscope/examples.rst | 9 --------- 2 files changed, 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08ff814d22..4b5dde5b0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1899,7 +1899,6 @@ #### [niscope] Unreleased - Added - (Common) Added `nitlsconfig[grpc]>=1.0.0a2` to the optional gRPC dependencies. - - Added the `niscope_digital_filtering.py` example. - Added the `niscope_external_clocking.py` example. - Changed - Removed diff --git a/docs/niscope/examples.rst b/docs/niscope/examples.rst index 25f1c497da..f7308ebf4b 100644 --- a/docs/niscope/examples.rst +++ b/docs/niscope/examples.rst @@ -3,15 +3,6 @@ Examples `You can download all niscope examples for latest version here `_ -niscope_digital_filtering.py ----------------------------- - -.. literalinclude:: ../../src/niscope/examples/niscope_digital_filtering.py - :language: python - :linenos: - :encoding: utf8 - :caption: `(niscope_digital_filtering.py) `_ - niscope_external_clocking.py ---------------------------- From f02aaad3acc68f26945e0b900cdadd679c7f6b16 Mon Sep 17 00:00:00 2001 From: NI-velmurugan Date: Mon, 26 Oct 2026 10:12:19 +0000 Subject: [PATCH 3/4] Updating the CLI params name --- src/niscope/examples/niscope_external_clocking.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/niscope/examples/niscope_external_clocking.py b/src/niscope/examples/niscope_external_clocking.py index f220463445..badb3e0867 100644 --- a/src/niscope/examples/niscope_external_clocking.py +++ b/src/niscope/examples/niscope_external_clocking.py @@ -17,7 +17,6 @@ def _create_chart(plt): def _update_chart(axes, waveforms, actual_sample_rate): - # One line per channel is created on the first acquisition and updated afterwards if len(axes.lines) < len(waveforms): while len(axes.lines) < len(waveforms): axes.plot([], [], label=waveforms[len(axes.lines)].channel) @@ -31,13 +30,12 @@ def _update_chart(axes, waveforms, actual_sample_rate): axes.autoscale_view() -def example(resource_name, channels, options, min_record_length, range, offset, source, rate, divisor, multiplier, total_iterations): - # Imported lazily because matplotlib is unavailable on some platforms (e.g. 32-bit Python) +def example(resource_name, channels, options, min_record_length, voltage_range, voltage_offset, source, rate, divisor, multiplier, total_iterations): import matplotlib.pyplot as plt with niscope.Session(resource_name=resource_name, options=options) as session: channel_session = session.channels[channels] - channel_session.configure_vertical(range=range, coupling=niscope.VerticalCoupling.DC, offset=offset) + channel_session.configure_vertical(range=voltage_range, coupling=niscope.VerticalCoupling.DC, offset=voltage_offset) session.configure_trigger_immediate() session.horz_min_num_pts = min_record_length session.samp_clk_timebase_src = source @@ -70,8 +68,8 @@ def _main(argsv): parser = argparse.ArgumentParser(description='Continuously acquires and plots a waveform using an external sample clock.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('-n', '--resource-name', default='PXI1Slot3', help='Specifies the resource name of the device to initialize.') parser.add_argument('-c', '--channels', default='0', help='Comma-separated list of channels to plot, one line per channel') - parser.add_argument('-v', '--range', default=1.0, type=float, help='Specifies the absolute value of the input range for a channel in volts.') - parser.add_argument('--offset', default=0.0, type=float, help='Specifies the location of the center of the range. The value is with respect to ground and is in volts.') + parser.add_argument('-v', '--voltage-range', default=1.0, type=float, help='Specifies the absolute value of the input range for a channel in volts.') + parser.add_argument('--voltage-offset', default=0.0, type=float, help='Specifies the location of the center of the range. The value is with respect to ground and is in volts.') parser.add_argument('-l', '--min-record-length', default=1000, type=int, help='Specifies the minimum number of points you require in the waveform record for each channel.') parser.add_argument('-s', '--source', default='VAL_CLK_IN', type=str, help='Specifies the source of the sample clock timebase, which is the timebase used to control waveform sampling.') parser.add_argument('-r', '--rate', default=1e8, type=float, help='If samp_clk_timebase_src is an external source, specifies the frequency in hertz of the external clock used as the timebase source.') @@ -80,7 +78,7 @@ def _main(argsv): parser.add_argument('--total-iterations', default=None, type=int, help='Stops after this many acquisitions and keeps the chart open until its window is closed; omit to run until the chart window is closed or Ctrl+C is pressed.') 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.channels, args.option_string, args.min_record_length, args.range, args.offset, args.source, args.rate, args.divisor, args.multiplier, args.total_iterations) + example(args.resource_name, args.channels, args.option_string, args.min_record_length, args.voltage_range, args.voltage_offset, args.source, args.rate, args.divisor, args.multiplier, args.total_iterations) def main(): From 00ed2228926e6fc0408c97e2fa08ad466cd0a59f Mon Sep 17 00:00:00 2001 From: NI-velmurugan Date: Tue, 27 Oct 2026 09:01:50 +0000 Subject: [PATCH 4/4] Rephrasing the comments --- src/niscope/examples/niscope_external_clocking.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/niscope/examples/niscope_external_clocking.py b/src/niscope/examples/niscope_external_clocking.py index badb3e0867..a8c8c62894 100644 --- a/src/niscope/examples/niscope_external_clocking.py +++ b/src/niscope/examples/niscope_external_clocking.py @@ -47,7 +47,7 @@ def example(resource_name, channels, options, min_record_length, voltage_range, iterations = 0 try: - # A total_iterations of None runs until the chart window is closed or Ctrl+C is pressed + # Run until the chart is closed, Ctrl+C is pressed, or the iteration limit is reached. while plt.fignum_exists(figure.number) and (total_iterations is None or iterations < total_iterations): # read() initiates the acquisition, waits for it to complete and fetches the data waveforms = channel_session.read(num_samples=min_record_length) @@ -55,7 +55,7 @@ def example(resource_name, channels, options, min_record_length, voltage_range, iterations += 1 plt.pause(0.01) - # The acquisition has stopped; keep the final chart on screen until its window is closed + # Keep the final plot window open after acquisition stops until the user closes it. if total_iterations is not None and plt.fignum_exists(figure.number): plt.show() except KeyboardInterrupt: