# 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)