Skip to content

CounterAndTimer


# coding=utf-8
# =============================================================================
# Copyright (c) 2026 FLIR Integrated Imaging Solutions, Inc. All Rights Reserved.
#
# This software is the confidential and proprietary information of FLIR
# Integrated Imaging Solutions, Inc. ("Confidential Information"). You
# shall not disclose such Confidential Information and shall use it only in
# accordance with the terms of the license agreement you entered into
# with FLIR Integrated Imaging Solutions, Inc. (FLIR).
#
# FLIR MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
# SOFTWARE, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
# PURPOSE, OR NON-INFRINGEMENT. FLIR SHALL NOT BE LIABLE FOR ANY DAMAGES
# SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
# THIS SOFTWARE OR ITS DERIVATIVES.
# =============================================================================
#
# CounterAndTimer.py shows how to setup a Pulse Width Modulation (PWM)
# signal using counters and timers. The camera will output the PWM signal via
# strobe, and capture images at a rate defined by the PWM signal as well.
# Users should take care to use a PWM signal within the camera's max
# frame rate (by default, the PWM signal is set to 50 Hz).
#
# Counter and Timer functionality is only available for BFS and Oryx Cameras.
# For details on the hardware setup, see our kb article, "Using Counter and
# Timer Control"; https://www.teledynevisionsolutions.com/support/support-center/application-note/iis/using-counter-and-timer-control/
#
# Please leave us feedback at: https://www.surveymonkey.com/r/TDYMVAPI
# More source code examples at: https://github.com/Teledyne-MV/Spinnaker-Examples
# Need help? Check out our forum at: https://teledynevisionsolutions.zendesk.com/hc/en-us/community/topics

import os
import PySpin
import sys

NUM_IMAGES = 10  # number of images to grab


def print_device_info(nodemap):
    """
    This function prints the device information of the camera from the transport
    layer; please see NodeMapInfo example for more in-depth comments on printing
    device information from the nodemap.

    :param nodemap: Device nodemap.
    :type nodemap: INodeMap
    :return: True if successful, False otherwise.
    :rtype: bool
    """

    print('*** DEVICE INFORMATION ***\n')

    try:
        result = True
        node_device_information = PySpin.CCategoryPtr(nodemap.GetNode('DeviceInformation'))

        if PySpin.IsReadable(node_device_information):
            features = node_device_information.GetFeatures()
            for feature in features:
                node_feature = PySpin.CValuePtr(feature)
                feature_string = node_feature.ToString() if PySpin.IsReadable(node_feature) else 'Node not readable'
                print('{}: {}'.format(node_feature.GetName(), feature_string))

        else:
            print('Device control information not readable.')

        print('')

    except PySpin.SpinnakerException as ex:
        print('Error: {}'.format(ex))
        return False

    return result


def setup_counter_and_timer(nodemap):
    """
    This function configures the camera to setup a Pulse Width Modulation signal using
    Counter and Timer functionality.  By default, the PWM signal will be set to run at
    50hz, with a duty cycle of 70%.

    :param nodemap: Device nodemap.
    :type nodemap: INodeMap
    :return: True if successful, False otherwise.
    :rtype: bool
    """

    print('Configuring Pulse Width Modulation signal')

    try:
        result = True

        # Set Counter Selector to Counter 0
        node_counter_selector = PySpin.CEnumerationPtr(nodemap.GetNode('CounterSelector'))

        # Check to see if camera supports Counter and Timer functionality
        if not PySpin.IsAvailable(node_counter_selector):
            print('\nCamera does not support Counter and Timer Functionality.  Aborting...\n')
            return False

        if not PySpin.IsReadable(node_counter_selector) or not PySpin.IsWritable(node_counter_selector):
            print('\nUnable to get or set Counter Selector (enumeration retrieval). Aborting...\n')
            return False

        entry_counter_0 = node_counter_selector.GetEntryByName('Counter0')
        if not PySpin.IsReadable(entry_counter_0):
            print('\nUnable to set Counter Selector (entry retrieval). Aborting...\n')
            return False

        counter_0 = entry_counter_0.GetValue()

        node_counter_selector.SetIntValue(counter_0)

        # Set Counter Event Source to MHzTick
        node_counter_event_source = PySpin.CEnumerationPtr(nodemap.GetNode('CounterEventSource'))
        if not PySpin.IsReadable(node_counter_event_source) or not PySpin.IsWritable(node_counter_event_source):
            print('\nUnable to set Counter Event Source (enumeration retrieval). Aborting...\n')
            return False

        entry_counter_event_source_mhz_tick = node_counter_event_source.GetEntryByName('MHzTick')
        if not PySpin.IsReadable(entry_counter_event_source_mhz_tick):
            print('\nUnable to set Counter Event Source (entry retrieval). Aborting...\n')
            return False

        counter_event_source_mhz_tick = entry_counter_event_source_mhz_tick.GetValue()

        node_counter_event_source.SetIntValue(counter_event_source_mhz_tick)

        # Set Counter Duration to 14000
        node_counter_duration = PySpin.CIntegerPtr(nodemap.GetNode('CounterDuration'))
        if not PySpin.IsWritable(node_counter_duration):
            print('\nUnable to set Counter Duration (integer retrieval). Aborting...\n')
            return False

        node_counter_duration.SetValue(14000)

        # Set Counter Delay to 6000
        node_counter_delay = PySpin.CIntegerPtr(nodemap.GetNode('CounterDelay'))
        if not PySpin.IsWritable(node_counter_delay):
            print('\nUnable to set Counter Delay (integer retrieval). Aborting...\n')
            return False

        node_counter_delay.SetValue(6000)

        # Determine Duty Cycle of PWM signal
        duty_cycle = float(node_counter_duration.GetValue()) / (float(node_counter_duration.GetValue() +
                                                                      node_counter_delay.GetValue())) * 100

        print('\nThe duty cycle has been set to {}%'.format(duty_cycle))

        # Determine pulse rate of PWM signal
        pulse_rate = 1000000 / float(node_counter_duration.GetValue() + node_counter_delay.GetValue())

        print('\nThe pulse rate has been set to {} Hz'.format(pulse_rate))

        # Set Counter Trigger Source to Frame Trigger Wait
        node_counter_trigger_source = PySpin.CEnumerationPtr(nodemap.GetNode('CounterTriggerSource'))
        if not PySpin.IsReadable(node_counter_trigger_source) or not PySpin.IsWritable(node_counter_trigger_source):
            print('\nUnable to set Counter Trigger Source (enumeration retrieval). Aborting...\n')
            return False

        entry_counter_trigger_source_ftw = node_counter_trigger_source.GetEntryByName('FrameTriggerWait')
        if not PySpin.IsReadable(entry_counter_trigger_source_ftw):
            print('\nUnable to set Counter Trigger Source (entry retrieval). Aborting...\n')
            return False

        counter_trigger_source_ftw = entry_counter_trigger_source_ftw.GetValue()

        node_counter_trigger_source.SetIntValue(counter_trigger_source_ftw)

        # Set Counter Trigger Activation to Level High
        node_counter_trigger_activation = PySpin.CEnumerationPtr(nodemap.GetNode('CounterTriggerActivation'))
        if not PySpin.IsReadable(node_counter_trigger_activation) or not PySpin.IsWritable(node_counter_trigger_activation):
            print('\nUnable to set Counter Trigger Activation (enumeration retrieval). Aborting...\n')
            return False

        entry_counter_trigger_source_lh = node_counter_trigger_activation.GetEntryByName('LevelHigh')
        if not PySpin.IsReadable(entry_counter_trigger_source_lh):
            print('\nUnable to set Counter Trigger Activation (entry retrieval). Aborting...\n')
            return False

        counter_trigger_level_high = entry_counter_trigger_source_lh.GetValue()

        node_counter_trigger_activation.SetIntValue(counter_trigger_level_high)

    except PySpin.SpinnakerException as ex:
        print('Error: {}'.format(ex))
        return False

    return result


def configure_digital_io(nodemap):
    """
    This function configures the GPIO to output the PWM signal.

    :param nodemap: Device nodemap.
    :type nodemap: INodeMap
    :return: True if successful, False otherwise.
    :rtype: bool
    """

    print('\nConfiguring GPIO strobe output')

    try:
        result = True
        camera_family_bfs = "BFS"
        camera_family_oryx = "ORX"

        # Determine camera family
        node_device_name = PySpin.CStringPtr(nodemap.GetNode('DeviceModelName'))
        if not PySpin.IsReadable(node_device_name):
            print('\nUnable to determine camera family. Aborting...\n')
            return False

        camera_model = node_device_name.GetValue()

        # Set Line Selector
        node_line_selector = PySpin.CEnumerationPtr(nodemap.GetNode('LineSelector'))
        if not PySpin.IsReadable(node_line_selector) or not PySpin.IsWritable(node_line_selector):
            print('\nUnable to get or set Line Selector (enumeration retrieval). Aborting...\n')
            return False

        if camera_family_bfs in camera_model:

            entry_line_selector_line_1 = node_line_selector.GetEntryByName('Line1')
            if not PySpin.IsReadable(entry_line_selector_line_1):
                print('\nUnable to set Line Selector (entry retrieval). Aborting...\n')
                return False

            line_selector_line_1 = entry_line_selector_line_1.GetValue()

            node_line_selector.SetIntValue(line_selector_line_1)

        elif camera_family_oryx in camera_model:

            entry_line_selector_line_2 = node_line_selector.GetEntryByName('Line2')
            if not PySpin.IsReadable(entry_line_selector_line_2):
                print('\nUnable to set Line Selector (entry retrieval). Aborting...\n')
                return False

            line_selector_line_2 = entry_line_selector_line_2.GetValue()

            node_line_selector.SetIntValue(line_selector_line_2)

            # Set Line Mode to output
            node_line_mode = PySpin.CEnumerationPtr(nodemap.GetNode('LineMode'))
            if not PySpin.IsReadable(node_line_mode) or not PySpin.IsWritable(node_line_mode):
                print('\nUnable to set Line Mode (enumeration retrieval). Aborting...\n')
                return False

            entry_line_mode_output = node_line_mode.GetEntryByName('Output')
            if not PySpin.IsReadable(entry_line_mode_output):
                print('\nUnable to set Line Mode (entry retrieval). Aborting...\n')
                return False

            line_mode_output = entry_line_mode_output.GetValue()

            node_line_mode.SetIntValue(line_mode_output)

        # Set Line Source for Selected Line to Counter 0 Active
        node_line_source = PySpin.CEnumerationPtr(nodemap.GetNode('LineSource'))
        if not PySpin.IsReadable(node_line_source) or not PySpin.IsWritable(node_line_source):
            print('\nUnable to get or set Line Source (enumeration retrieval). Aborting...\n')
            return False

        entry_line_source_counter_0_active = node_line_source.GetEntryByName('Counter0Active')
        if not PySpin.IsReadable(entry_line_source_counter_0_active):
            print('\nUnable to set Line Source (entry retrieval). Aborting...\n')
            return False

        line_source_counter_0_active = entry_line_source_counter_0_active.GetValue()

        node_line_source.SetIntValue(line_source_counter_0_active)

        if camera_family_bfs in camera_model:
            # Change Line Selector to Line 2 and Enable 3.3 Voltage Rail
            entry_line_selector_line_2 = node_line_selector.GetEntryByName('Line2')
            if not PySpin.IsReadable(entry_line_selector_line_2):
                print('\nUnable to set Line Selector (entry retrieval). Aborting...\n')
                return False

            line_selector_line_2 = entry_line_selector_line_2.GetValue()

            node_line_selector.SetIntValue(line_selector_line_2)

            node_voltage_enable = PySpin.CBooleanPtr(nodemap.GetNode('V3_3Enable'))
            if not PySpin.IsWritable(node_voltage_enable):
                print('\nUnable to set Voltage Enable (boolean retrieval). Aborting...\n')
                return False

            node_voltage_enable.SetValue(True)

    except PySpin.SpinnakerException as ex:
        print('Error: {}'.format(ex))
        return False

    return result


def configure_exposure_and_trigger(nodemap):
    """
    This function configures the camera to set a manual exposure value and enables
    camera to be triggered by the PWM signal.

    :param nodemap: Device nodemap.
    :type nodemap: INodeMap
    :return: True if successful, False otherwise.
    :rtype: bool
    """

    print('\nConfiguring Exposure and Trigger')

    try:
        result = True

        # Turn off auto exposure
        node_exposure_auto = PySpin.CEnumerationPtr(nodemap.GetNode('ExposureAuto'))
        if not PySpin.IsReadable(node_exposure_auto) or not PySpin.IsWritable(node_exposure_auto):
            print('\nUnable to set Exposure Auto (enumeration retrieval). Aborting...\n')
            return False

        entry_exposure_auto_off = node_exposure_auto.GetEntryByName('Off')
        if not PySpin.IsReadable(entry_exposure_auto_off):
            print('\nUnable to set Exposure Auto (entry retrieval). Aborting...\n')
            return False

        exposure_auto_off = entry_exposure_auto_off.GetValue()

        node_exposure_auto.SetIntValue(exposure_auto_off)

        # Set Exposure Time to less than 1/50th of a second (5000 us is used as an example)
        node_exposure_time = PySpin.CFloatPtr(nodemap.GetNode('ExposureTime'))
        if not PySpin.IsWritable(node_exposure_time):
            print('\nUnable to set Exposure Time (float retrieval). Aborting...\n')
            return False

        node_exposure_time.SetValue(5000)

        # Ensure trigger mode is off
        #
        # *** NOTES ***
        # The trigger must be disabled in order to configure
        node_trigger_mode = PySpin.CEnumerationPtr(nodemap.GetNode('TriggerMode'))
        if not PySpin.IsReadable(node_trigger_mode) or not PySpin.IsWritable(node_trigger_mode):
            print('\nUnable to disable trigger mode (node retrieval). Aborting...\n')
            return False

        entry_trigger_mode_off = node_trigger_mode.GetEntryByName('Off')
        if not PySpin.IsReadable(entry_trigger_mode_off):
            print('\nUnable to disable trigger mode (enum entry retrieval). Aborting...\n')
            return False

        node_trigger_mode.SetIntValue(entry_trigger_mode_off.GetValue())

        # Set Trigger Source to Counter 0 Start
        node_trigger_source = PySpin.CEnumerationPtr(nodemap.GetNode('TriggerSource'))
        if not PySpin.IsReadable(node_trigger_source) or not PySpin.IsWritable(node_trigger_source):
            print('\nUnable to get or get trigger source (enumeration retrieval). Aborting...\n')
            return False

        entry_trigger_source_counter_0_start = node_trigger_source.GetEntryByName('Counter0Start')
        if not PySpin.IsReadable(entry_trigger_source_counter_0_start):
            print('\nUnable to get trigger mode (enum entry retrieval). Aborting...\n')
            return False

        node_trigger_source.SetIntValue(entry_trigger_source_counter_0_start.GetValue())

        # Set Trigger Overlap to Readout
        node_trigger_overlap = PySpin.CEnumerationPtr(nodemap.GetNode('TriggerOverlap'))
        if not PySpin.IsReadable(node_trigger_overlap) or not PySpin.IsWritable(node_trigger_overlap):
            print('\nUnable to set Trigger Overlap (enumeration retrieval). Aborting...\n')
            return False

        entry_trigger_overlap_ro = node_trigger_overlap.GetEntryByName('ReadOut')
        if not PySpin.IsReadable(entry_trigger_overlap_ro):
            print('\nUnable to set Trigger Overlap (entry retrieval). Aborting...\n')
            return False

        trigger_overlap_ro = entry_trigger_overlap_ro.GetValue()

        node_trigger_overlap.SetIntValue(trigger_overlap_ro)

        # Turn trigger mode on
        entry_trigger_mode_on = node_trigger_mode.GetEntryByName('On')
        if not PySpin.IsReadable(entry_trigger_mode_on):
            print('\nUnable to enable trigger mode (enum entry retrieval). Aborting...\n')
            return False

        node_trigger_mode.SetIntValue(entry_trigger_mode_on.GetValue())

    except PySpin.SpinnakerException as ex:
        print('Error: {}'.format(ex))
        return False

    return result


def acquire_images(cam, nodemap, nodemap_tldevice):
    """
    This function acquires and saves 10 images from a device; please see
    Acquisition example for more in-depth comments on acquiring images.

    :param cam: Camera to acquire images from.
    :param nodemap: Device nodemap.
    :param nodemap_tldevice: Transport layer device nodemap.
    :type cam: CameraPtr
    :type nodemap: INodeMap
    :type nodemap_tldevice: INodeMap
    :return: True if successful, False otherwise.
    :rtype: bool
    """

    print('\n*** IMAGE ACQUISITION ***\n')
    try:
        result = True

        # Set acquisition mode to continuous
        node_acquisition_mode = PySpin.CEnumerationPtr(nodemap.GetNode('AcquisitionMode'))
        if not PySpin.IsReadable(node_acquisition_mode) or not PySpin.IsWritable(node_acquisition_mode):
            print('Unable to set acquisition mode to continuous (enumeration retrieval). Aborting...\n')
            return False

        entry_acquisition_mode_continuous = node_acquisition_mode.GetEntryByName('Continuous')
        if not PySpin.IsReadable(entry_acquisition_mode_continuous):
            print('Unable to set acquisition mode to continuous (enum entry retrieval). Aborting...\n')
            return False

        acquisition_mode_continuous = entry_acquisition_mode_continuous.GetValue()

        node_acquisition_mode.SetIntValue(acquisition_mode_continuous)

        print('Acquisition mode set to continuous...')

        #  Begin acquiring images
        cam.BeginAcquisition()

        print('Acquiring images...')

        #  Retrieve device serial number for filename
        device_serial_number = ''
        node_device_serial_number = PySpin.CStringPtr(nodemap_tldevice.GetNode('DeviceSerialNumber'))
        if PySpin.IsReadable(node_device_serial_number):
            device_serial_number = node_device_serial_number.GetValue()
            print('Device serial number retrieved as {}...'.format(device_serial_number))

        print('')

        # Retrieve, convert, and save images

        # Create ImageProcessor instance for post processing images
        processor = PySpin.ImageProcessor()

        # Set default image processor color processing method
        #
        # *** NOTES ***
        # By default, if no specific color processing algorithm is set, the image
        # processor will default to NEAREST_NEIGHBOR method.
        processor.SetColorProcessing(PySpin.SPINNAKER_COLOR_PROCESSING_ALGORITHM_HQ_LINEAR)

        for i in range(NUM_IMAGES):
            try:

                #  Retrieve next received image and ensure image completion
                image_result = cam.GetNextImage(1000)

                if image_result.IsIncomplete():
                    print('Image incomplete with image status {} ...'.format(image_result.GetImageStatus()))

                else:

                    #  Print image information; height and width recorded in pixels
                    width = image_result.GetWidth()
                    height = image_result.GetHeight()
                    print('Grabbed image {}, width = {}, height = {}'.format(i, width, height))

                    #  Convert image to mono 8
                    image_converted = processor.Convert(image_result, PySpin.PixelFormat_Mono8)

                    # Create a unique filename
                    if device_serial_number:
                        filename = 'CounterAndTimer-{}-{}.jpg'.format(device_serial_number, i)
                    else:  # if serial number is empty
                        filename = 'CounterAndTimer-{}.jpg'.format(i)

                    #  Save image
                    image_converted.Save(filename)
                    print('Image saved at {}'.format(filename))

                    #  Release image
                    image_result.Release()
                    print('')

            except PySpin.SpinnakerException as ex:
                print('Error: {}'.format(ex))
                return False

        #  End acquisition
        cam.EndAcquisition()

    except PySpin.SpinnakerException as ex:
        print('Error: {}'.format(ex))
        return False

    return result


def reset_trigger(nodemap):
    """
    This function returns the camera to a normal state by turning off trigger mode.

    *** NOTES ***
    This function turns off trigger mode, but does not change the trigger source.

    :param nodemap: Transport layer device nodemap.
    :type nodemap: INodeMap
    :returns: True if successful, False otherwise.
    :rtype: bool
    """

    try:
        result = True

        # Turn trigger mode back off
        node_trigger_mode = PySpin.CEnumerationPtr(nodemap.GetNode('TriggerMode'))
        if not PySpin.IsReadable(node_trigger_mode) or not PySpin.IsWritable(node_trigger_mode):
            print('Unable to disable trigger mode (node retrieval). Non-fatal error...\n')

        entry_trigger_mode_off = node_trigger_mode.GetEntryByName('Off')
        if not PySpin.IsReadable(entry_trigger_mode_off):
            print('Unable to disable trigger mode (enum entry retrieval). Non-fatal error...\n')

        node_trigger_mode.SetIntValue(entry_trigger_mode_off.GetValue())

    except PySpin.SpinnakerException as ex:
        print('Error: {}'.format(ex))
        return False

    return result


def run_single_camera(cam):
    """
    This function acts as the body of the example; please see the NodeMapInfo example
    for more in-depth comments on setting up cameras.

    :param cam: Camera to run on.
    :type cam: CameraPtr
    :return: True if successful, False otherwise.
    :rtype: bool
    """
    try:
        result = True

        # Retrieve TL device nodemap and print device information
        nodemap_tldevice = cam.GetTLDeviceNodeMap()

        result &= print_device_info(nodemap_tldevice)

        # Initialize camera
        cam.Init()

        # Retrieve GenICam nodemap
        nodemap = cam.GetNodeMap()

        # Configure Counter and Timer setup
        result &= setup_counter_and_timer(nodemap)
        if not result:
            return result

        # Configure DigitalIO (GPIO output)
        result &= configure_digital_io(nodemap)
        if not result:
            return result

        # Configure Exposure and Trigger
        result &= configure_exposure_and_trigger(nodemap)
        if not result:
            return result

        # Acquire images
        result &= acquire_images(cam, nodemap, nodemap_tldevice)

        # Reset trigger
        result &= reset_trigger(nodemap)

        # Deinitialize camera
        cam.DeInit()

    except PySpin.SpinnakerException as ex:
        print('Error: {}'.format(ex))
        result = False

    return result


def main():
    """
    Example entry point; please see Enumeration example for more in-depth
    comments on preparing and cleaning up the system.

    :return: True if successful, False otherwise.
    :rtype: bool
    """

    # Since this application saves images in the current folder
    # we must ensure that we have permission to write to this folder.
    # If we do not have permission, fail right away.
    try:
        test_file = open('test.txt', 'w+')
    except IOError:
        print('Unable to write to current directory. Please check permissions.')
        input('Press Enter to exit...')
        return False

    test_file.close()
    os.remove(test_file.name)

    result = True

    # Retrieve singleton reference to system object
    system = PySpin.System.GetInstance()

    # Get current library version
    version = system.GetLibraryVersion()
    print('Library version: {}.{}.{}.{}'.format(version.major, version.minor, version.type, version.build))

    # Retrieve list of cameras from the system
    cam_list = system.GetCameras()

    num_cameras = cam_list.GetSize()

    print('Number of cameras detected: {}'.format(num_cameras))

    # Finish if there are no cameras
    if num_cameras == 0:

        # Clear camera list before releasing system
        cam_list.Clear()

        # Release system instance
        system.ReleaseInstance()

        print('Not enough cameras!')
        input('Done! Press Enter to exit...')

        return False

    # Run example on each camera
    for i, cam in enumerate(cam_list):

        print('Running example for camera {}...'.format(i))

        result &= run_single_camera(cam)
        print('Camera {} example complete... \n'.format(i))

    # Release reference to camera
    # NOTE: Unlike the C++ examples, we cannot rely on pointer objects being automatically
    # cleaned up when going out of scope.
    # The usage of del is preferred to assigning the variable to None.
    del cam

    # Clear camera list before releasing system
    cam_list.Clear()

    # Release system instance
    system.ReleaseInstance()

    input('Done! Press Enter to exit...')
    return result

if __name__ == '__main__':
    if main():
        sys.exit(0)
    else:
        sys.exit(1)