# 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.
# =============================================================================
#
# NodeMapCallback.py shows how to use nodemap callbacks. It relies
# on information provided in the Enumeration, Acquisition, and NodeMapInfo
# examples. As callbacks are very similar to events, it may be a good idea to
# explore this example prior to tackling the events examples.
#
# This example focuses on creating, registering, using, and unregistering
# callbacks. A callback requires a callback class with a callback function signature,
# which allows it to be registered to and access a node. Events follow this same pattern.
#
# Once comfortable with NodeMapCallback, we suggest checking out any of the
# events examples: EnumerationEvents, ImageEvents, or Logging.
#
# 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 PySpin
import sys
NUM_IMAGES = 10 # number of images to grab
class HeightNodeCallback(PySpin.NodeCallback):
"""
This is the first of three callback classes. This callback will be registered to the height node.
Node callbacks must inherit from NodeCallback, and must implement CallbackFunction with the same function signature.
NOTE: Instances of callback classes must not go out of scope until they are deregistered, otherwise segfaults
will occur.
"""
def __init__(self):
super(HeightNodeCallback, self).__init__()
def CallbackFunction(self, node):
"""
This function gets called when the height node changes and triggers a callback.
:param node: Height node.
:type node: INode
:rtype: None
"""
node_height = PySpin.CIntegerPtr(node)
print('Height callback message:\n\tLook! Height changed to %f...\n' % node_height.GetValue())
class GainNodeCallback(PySpin.NodeCallback):
"""
This is the second callback class, registered to the gain node.
"""
def __init__(self):
super(GainNodeCallback, self).__init__()
def CallbackFunction(self, node):
"""
This function gets called when the gain node changes and triggers a callback.
:param node: Gain node.
:type node: INode
:rtype: None
"""
node_gain = PySpin.CFloatPtr(node)
print('Gain callback message:\n\tLook! Gain changed to %f...\n' % node_gain.GetValue())
class EventNodeCallback(PySpin.NodeCallback):
"""
This is the third callback class, registered to event feature nodes.
"""
def __init__(self):
super(EventNodeCallback, self).__init__()
def CallbackFunction(self, event_node):
"""
This function gets called when the event node changes and triggers a callback.
:param node: Event node.
:type node: INode
:rtype: None
"""
node_name = event_node.GetName()
if event_node.GetPrincipalInterfaceType() == PySpin.intfIInteger:
integer = PySpin.CIntegerPtr(event_node)
print('%s callback message:\n\tValue changed to %f...\n' % (node_name, integer.GetValue()))
elif event_node.GetPrincipalInterfaceType() == PySpin.intfIBoolean:
boolean = PySpin.CBooleanPtr(event_node)
print('%s callback message:\n\tValue changed to %f...\n' % (node_name, boolean.GetValue()))
elif event_node.GetPrincipalInterfaceType() == PySpin.intfIFloat:
float = PySpin.CFloatPtr(event_node)
print('%s callback message:\n\tValue changed to %f...\n' % (node_name, float.GetValue()))
elif event_node.GetPrincipalInterfaceType() == PySpin.intfIString:
string = PySpin.CStringPtr(event_node)
print('%s callback message:\n\tValue changed to %s...\n' % (node_name, string.GetValue()))
else:
print('%s callback message:\n\t node with type %s was updated.\n' % (node_name, event_node.GetPrincipalInterfaceType()))
def configure_callbacks(nodemap, callback_list):
"""
This function sets up the example by disabling automatic gain, creating the callbacks, and registering them to
their specific nodes.
:param nodemap: Device nodemap.
:param callback_list: List of NodeCallbacks
:type nodemap: INodeMap
:returns: tuple (result, callback_list)
WHERE
result is True if successful, False otherwise
callback_list is the list of NodeCallback instances registered
callback_height is the HeightNodeCallback instance registered to the height node
callback_gain is the GainNodeCallback instance registered to the gain node
:rtype: (bool, list)
"""
print('\n*** CONFIGURING CALLBACKS ***\n')
try:
result = True
# Turn off automatic gain
#
# *** NOTES ***
# Automatic gain prevents the manual configuration of gain and needs to
# be turned off for this example.
#
# *** LATER ***
# Automatic exposure is turned off at the end of the example in order
# to restore the camera to its default state.
node_gain_auto = PySpin.CEnumerationPtr(nodemap.GetNode('GainAuto'))
if not PySpin.IsReadable(node_gain_auto) or not PySpin.IsWritable(node_gain_auto):
print('Unable to disable automatic gain (node retrieval). Aborting...')
return False, None
node_gain_auto_off = PySpin.CEnumEntryPtr(node_gain_auto.GetEntryByName('Off'))
if not PySpin.IsReadable(node_gain_auto_off):
print('Unable to disable automatic gain (enum entry retrieval). Aborting...')
return False, None
node_gain_auto.SetIntValue(node_gain_auto_off.GetValue())
print('Automatic gain disabled...')
# Register callback to height node
#
# *** NOTES ***
# Callbacks need to be registered to nodes, which should be writable
# if the callback is to ever be triggered. Also ensure that the callback
# instance does not go out of scope, as it will get garbage-collected
# and a segfault will result once the callback actually occurs.
#
# *** LATER ***
# Each callback needs to be unregistered individually before releasing
# the system or an exception will be thrown.
node_height = PySpin.CIntegerPtr(nodemap.GetNode('Height'))
if not PySpin.IsReadable(node_height) or not PySpin.IsWritable(node_height):
print('Unable to retrieve height. Aborting...\n')
return False, None
print('Height ready...')
callback_height = HeightNodeCallback()
PySpin.RegisterNodeCallback(node_height.GetNode(), callback_height)
callback_list.append(callback_height)
print('Height callback registered...')
# Register callback to gain node
#
# *** NOTES ***
# Depending on the specific goal of the function, it can be important
# to notice the node type that a callback is registered to. Notice in
# the callback functions above that the callback registered to height
# casts its node as an integer whereas the callback registered to gain
# casts as a float.
#
# *** LATER ***
# Each callback needs to be unregistered individually before releasing
# the system or an exception will be thrown.
node_gain = PySpin.CFloatPtr(nodemap.GetNode('Gain'))
if not PySpin.IsReadable(node_gain) or not PySpin.IsWritable(node_gain):
print('Unable to retrieve gain. Aborting...\n')
return False, None
print('Gain ready...')
callback_gain = GainNodeCallback()
PySpin.RegisterNodeCallback(node_gain.GetNode(), callback_gain)
callback_list.append(callback_gain)
print('Gain callback registered...\n')
except PySpin.SpinnakerException as ex:
print('Error: %s' % ex)
result = False, None
return result, callback_list
def configure_event_callbacks(nodemap, callback_list):
print('\n*** CONFIGURING EVENT CALLBACKS ***\n')
try:
result = True
#
# Retrieve event selector
#
# *** NOTES ***
# Each type of event must be enabled individually. This is done
# by retrieving "EventSelector" (an enumeration node) and then enabling
# the specific event on "EventNotification" (another enumeration node).
#
event_selector = PySpin.CEnumerationPtr(nodemap.GetNode("EventSelector"))
if not PySpin.IsReadable(event_selector) or not PySpin.IsWritable(event_selector):
print('Unable to retrieve event selector entries. Skipping...\n')
return None, callback_list
entries = [PySpin.CEnumEntryPtr(event_selector_entry) for event_selector_entry in event_selector.GetEntries()]
print('Enabling event selector entries...')
#
# Enable device events
#
# *** NOTES ***
# In order to enable a specific event, the event selector and event
# notification nodes (both of type enumeration) must work in unison.
# The desired event must first be selected on the event selector node
# and then enabled on the event notification node.
#
for event_selector_entry in entries:
# Select entry on selector node
if not PySpin.IsReadable(event_selector_entry):
# Skip if node fails
continue
event_selector.SetIntValue(event_selector_entry.GetValue())
# Retrieve event notification node (an enumeration node)
event_notification = PySpin.CEnumerationPtr(nodemap.GetNode("EventNotification"))
# Retrieve entry node to enable device event
if not PySpin.IsReadable(event_notification):
# Skip if node fails
continue
event_notification_on = PySpin.CEnumEntryPtr(event_notification.GetEntryByName("On"))
if not PySpin.IsReadable(event_notification_on):
# Skip if node fails
continue
if not PySpin.IsWritable(event_notification):
# Skip if node fails
continue
event_notification.SetIntValue(event_notification_on.GetValue())
print('\n\t%s: enabled\n' % event_selector_entry.GetDisplayName())
# Register Event Data callbacks
entry_name = event_selector_entry.GetSymbolic()
data_category_name = 'Event' + entry_name + 'Data'
data_category = PySpin.CCategoryPtr(nodemap.GetNode(data_category_name))
if data_category:
features = data_category.GetFeatures()
for feature in features:
#
# Register callback to event data node
#
# *** LATER ***
# Each callback needs to be unregistered individually before releasing
# the system or an exception will be thrown.
#
feature_node = PySpin.CNodePtr(feature)
feature_name = feature_node.GetDisplayName()
callback_feature = EventNodeCallback()
PySpin.RegisterNodeCallback(feature.GetNode(), callback_feature)
callback_list.append(callback_feature)
print('\t%s callback registered...' %feature_node.GetName())
except PySpin.SpinnakerException as ex:
print('Error: %s' % ex)
result = False, None
return result, callback_list
def reset_events(nodemap):
try:
result = True
# Retrieve event selector
event_selector = PySpin.CEnumerationPtr(nodemap.GetNode("EventSelector"))
if not PySpin.IsReadable(event_selector) or not PySpin.IsWritable(event_selector):
print('Unable to retrieve event selector entries. Skipping...\n')
return result
entries = [PySpin.CEnumEntryPtr(event_selector_entry) for event_selector_entry in event_selector.GetEntries()]
print('Disabling event selector entries...')
# Disable device events
for event_selector_entry in entries:
# Select entry on selector node
if not PySpin.IsReadable(event_selector_entry):
# Skip if node fails
continue
event_selector.SetIntValue(event_selector_entry.GetValue())
# Retrieve event notification node (an enumeration node)
event_notification = PySpin.CEnumerationPtr(nodemap.GetNode("EventNotification"))
# Retrieve entry node to enable device event
if not PySpin.IsReadable(event_notification):
# Skip if node fails
continue
event_notification_off = PySpin.CEnumEntryPtr(event_notification.GetEntryByName("Off"))
if not PySpin.IsReadable(event_notification_off):
# Skip if node fails
continue
if not PySpin.IsWritable(event_notification):
# Skip if node fails
continue
event_notification.SetIntValue(event_notification_off.GetValue())
print('\n\t%s: disabled\n' % event_selector_entry.GetDisplayName())
except PySpin.SpinnakerException as ex:
print('Error: %s' % ex)
result = False, None
return result
def change_height_and_gain(nodemap):
"""
This function demonstrates the triggering of the nodemap callbacks. First it
changes height, which executes the callback registered to the height node, and
then it changes gain, which executes the callback registered to the gain node.
:param nodemap: Device nodemap.
:type nodemap: INodeMap
:return: True if successful, False otherwise.
:rtype: bool
""" # if not PySpin.IsReadable(event_node):
# print('Event callback triggered but node is not readable...\n')
# return
print('\n***CHANGE HEIGHT & GAIN ***\n')
try:
result = True
# Change height to trigger height callback
#
# *** NOTES ***
# Notice that changing the height only triggers the callback function
# registered to the height node.
node_height = PySpin.CIntegerPtr(nodemap.GetNode('Height'))
if not PySpin.IsReadable(node_height) or not PySpin.IsWritable(node_height) \
or node_height.GetInc() == 0 or node_height.GetMax() == 0:
print('Unable to retrieve height. Aborting...')
return False
height_to_set = node_height.GetMax()
print('Regular function message:\n\tHeight about to be changed to %i...\n' % height_to_set)
node_height.SetValue(height_to_set)
# Change gain to trigger gain callback
#
# *** NOTES ***
# The same is true of changing the gain node; changing a node will
# only ever trigger the callback function (or functions) currently
# registered to it.
node_gain = PySpin.CFloatPtr(nodemap.GetNode('Gain'))
if not PySpin.IsReadable(node_gain) or not PySpin.IsWritable(node_gain) or node_gain.GetMax() == 0:
print('Unable to retrieve gain...')
return False
gain_to_set = node_gain.GetMax() / 2.0
print('Regular function message:\n\tGain about to be changed to %f...\n' % gain_to_set)
node_gain.SetValue(gain_to_set)
except PySpin.SpinnakerException as ex:
print('Error: %s' % ex)
result = False
return result
def reset_callbacks(nodemap, callback_list):
"""
This function cleans up the example by deregistering the callbacks and
turning automatic gain back on.
:param nodemap: Device nodemap.
:param callback_list: List of node callback instances to deregister.
:type nodemap: INodeMap
:type callback_list: List
:return: True if successful, False otherwise.
:rtype: bool
"""
try:
result = True
for callback in callback_list:
# Deregister callbacks
#
# *** NOTES ***
# It is important to deregister each callback function from each node
# that it is registered to.
PySpin.DeregisterNodeCallback(callback)
print('Callbacks deregistered...')
# Turn automatic gain back on
#
# *** NOTES ***
# Automatic gain is turned back on in order to restore the camera to
# its default state.
node_gain_auto = PySpin.CEnumerationPtr(nodemap.GetNode('GainAuto'))
if not PySpin.IsReadable(node_gain_auto) or not PySpin.IsWritable(node_gain_auto):
print('Unable to enable automatic gain (node retrieval). Aborting...')
return False
node_gain_auto_continuous = PySpin.CEnumEntryPtr(node_gain_auto.GetEntryByName('Continuous'))
if not PySpin.IsReadable(node_gain_auto_continuous):
print('Unable to enable automatic gain (enum entry retrieval). Aborting...')
return False
node_gain_auto.SetIntValue(node_gain_auto_continuous.GetValue())
print('Automatic gain disabled...')
except PySpin.SpinnakerException as ex:
print('Error: %s' % ex)
result = False
return result
def acquire_images(cam, nodemap, nodemap_tldevice):
"""
This function acquires and saves 10 images from a device.
: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('*** 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 (enum retrieval). Aborting...')
return False
# Retrieve entry node from enumeration node
node_acquisition_mode_continuous = node_acquisition_mode.GetEntryByName('Continuous')
if not PySpin.IsReadable(node_acquisition_mode_continuous):
print('Unable to set acquisition mode to continuous (entry retrieval). Aborting...')
return False
# Retrieve integer value from entry node
acquisition_mode_continuous = node_acquisition_mode_continuous.GetValue()
# Set integer value from entry node as new value of enumeration node
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 %s...' % device_serial_number)
# 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
image_result = cam.GetNextImage(1000)
# Ensure image completion
if image_result.IsIncomplete():
print('Image incomplete with image status %d ...' % image_result.GetImageStatus())
else:
# Print image information; height and width recorded in pixels
width = image_result.GetWidth()
height = image_result.GetHeight()
print('Grabbed Image %d, width = %d, height = %d' % (i, width, height))
# Convert image to mono 8
image_converted = processor.Convert(image_result, PySpin.PixelFormat_Mono8)
# Release image
image_result.Release()
print('')
except PySpin.SpinnakerException as ex:
print('Error: %s' % ex)
return False
# End acquisition
cam.EndAcquisition()
except PySpin.SpinnakerException as ex:
print('Error: %s' % ex)
return False
return result
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: Transport layer device nodemap.
:type nodemap: INodeMap
:returns: 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)
print('%s: %s' % (node_feature.GetName(),
node_feature.ToString() if PySpin.IsReadable(node_feature) else 'Node not readable'))
else:
print('Device control information not readable.')
except PySpin.SpinnakerException as ex:
print('Error: %s' % ex)
return False
return result
def run_single_camera(cam):
"""
This function acts as the body of the example; please see NodeMapInfo example
for more in-depth comments on setting up cameras.
:param cam: Camera to setup and 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)
# Retrieve TL stream nodemap
nodemap_tlstream = cam.GetTLStreamNodeMap()
# Initialize camera
cam.Init()
# Retrieve GenICam nodemap
nodemap = cam.GetNodeMap()
# Configure callbacks
callback_list = []
err, callback_list = configure_callbacks(nodemap, callback_list)
if not err:
return err
# Configure event callbacks on remote device
err, callback_list = configure_event_callbacks(nodemap, callback_list)
if err is False and err is not None:
return err
# Configure event callbacks on local device
err, callback_list = configure_event_callbacks(nodemap_tldevice, callback_list)
if err is False and err is not None:
return err
# Configure event callbacks on local stream
err, callback_list = configure_event_callbacks(nodemap_tlstream, callback_list)
if err is False and err is not None:
return err
# Change height and gain to trigger callbacks
result &= change_height_and_gain(nodemap)
# Acquire images
result &= acquire_images(cam, nodemap, nodemap_tldevice)
# Reset callbacks
result &= reset_callbacks(nodemap, callback_list)
# Reset event notifications on remote device
result &= reset_events(nodemap)
# Reset event notifications on local device
result &= reset_events(nodemap_tldevice)
# Reset event notifications on local stream
result &= reset_events(nodemap_tlstream)
# Deinitialize camera
cam.DeInit()
except PySpin.SpinnakerException as ex:
print('Error: %s' % ex)
return 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
"""
result = True
# Retrieve singleton reference to system object
system = PySpin.System.GetInstance()
# Get current library version
version = system.GetLibraryVersion()
print('Library version: %d.%d.%d.%d' % (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: %d' % 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 %d...' % i)
result &= run_single_camera(cam)
print('Camera %d example complete... \n' % 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 instance
system.ReleaseInstance()
input('Done! Press Enter to exit...')
return result
if __name__ == '__main__':
if main():
sys.exit(0)
else:
sys.exit(1)