Overview
This comprehensive guide covers using PySpin (FLIR Spinnaker SDK) for capturing images from area scan cameras. PySpin provides Python bindings for the Spinnaker SDK, enabling powerful camera control and image acquisition capabilities.
Table of Contents
- System Setup
- Camera Discovery and Initialization
- Essential Camera Settings
- Image Acquisition
- Advanced Configuration
- Error Handling
- Best Practices
- Complete Example
System Setup
Prerequisites
- Hardware: FLIR/Teledyne area scan camera (Blackfly, Chameleon, etc.)
- Software: Spinnaker SDK installed
- Python: 3.6+ with PySpin package
- Dependencies: NumPy, OpenCV (optional for image processing)
Installation
# Install required packages
pip install numpy opencv-python
# PySpin is typically installed with Spinnaker SDK
# Verify installation
python -c "import PySpin; print('PySpin version:', PySpin.System.GetInstance().GetLibraryVersion())"
Camera Discovery and Initialization
Basic System Setup
import PySpin
import sys
def initialize_system():
"""Initialize the Spinnaker system and discover cameras"""
# Get singleton reference to system object
system = PySpin.System.GetInstance()
# Get library version
version = system.GetLibraryVersion()
print(f'Library version: {version.major}.{version.minor}.{version.type}.{version.build}')
return system
def discover_cameras(system):
"""Discover all connected cameras"""
# Get camera list
cam_list = system.GetCameras()
num_cameras = cam_list.GetSize()
print(f'Number of cameras detected: {num_cameras}')
if num_cameras == 0:
print('No cameras detected!')
return None, None
return cam_list, num_cameras
Camera Information Retrieval
def print_camera_info(cam):
"""Print detailed camera information"""
try:
# Get transport layer device nodemap
nodemap_tldevice = cam.GetTLDeviceNodeMap()
# Device information
node_device_info = PySpin.CCategoryPtr(nodemap_tldevice.GetNode('DeviceInformation'))
if PySpin.IsReadable(node_device_info):
features = node_device_info.GetFeatures()
print('\n*** CAMERA INFORMATION ***')
for feature in features:
node_feature = PySpin.CValuePtr(feature)
if PySpin.IsReadable(node_feature):
print(f'{node_feature.GetName()}: {node_feature.ToString()}')
# Get device serial number
node_serial = PySpin.CStringPtr(nodemap_tldevice.GetNode('DeviceSerialNumber'))
if PySpin.IsReadable(node_serial):
serial_number = node_serial.GetValue()
print(f'Serial Number: {serial_number}')
except PySpin.SpinnakerException as ex:
print(f'Error retrieving camera info: {ex}')
Essential Camera Settings
Stream Mode Configuration
def configure_stream_mode(cam, stream_mode='Socket'):
"""Configure camera stream mode (important for GigE cameras)"""
try:
# Get transport layer stream nodemap
nodemap_tlstream = cam.GetTLStreamNodeMap()
# Get stream mode node
node_stream_mode = PySpin.CEnumerationPtr(nodemap_tlstream.GetNode('StreamMode'))
if not PySpin.IsReadable(node_stream_mode) or not PySpin.IsWritable(node_stream_mode):
print('Stream mode not available')
return True
# Set stream mode
node_stream_mode_entry = PySpin.CEnumEntryPtr(node_stream_mode.GetEntryByName(stream_mode))
if PySpin.IsReadable(node_stream_mode_entry):
stream_mode_value = node_stream_mode_entry.GetValue()
node_stream_mode.SetIntValue(stream_mode_value)
print(f'Stream mode set to: {stream_mode}')
return True
except PySpin.SpinnakerException as ex:
print(f'Error setting stream mode: {ex}')
return False
Acquisition Mode Configuration
def configure_acquisition_mode(cam, mode='Continuous'):
"""Set acquisition mode (Continuous, SingleFrame, MultiFrame)"""
try:
# Get device nodemap
nodemap = cam.GetNodeMap()
# Set acquisition mode
node_acquisition_mode = PySpin.CEnumerationPtr(nodemap.GetNode('AcquisitionMode'))
if not PySpin.IsReadable(node_acquisition_mode) or not PySpin.IsWritable(node_acquisition_mode):
print('Acquisition mode not available')
return False
# Get the desired entry node
node_acquisition_mode_entry = node_acquisition_mode.GetEntryByName(mode)
if not PySpin.IsReadable(node_acquisition_mode_entry):
print(f'Acquisition mode {mode} not available')
return False
# Set the acquisition mode
acquisition_mode_value = node_acquisition_mode_entry.GetValue()
node_acquisition_mode.SetIntValue(acquisition_mode_value)
print(f'Acquisition mode set to: {mode}')
return True
except PySpin.SpinnakerException as ex:
print(f'Error setting acquisition mode: {ex}')
return False
Image Format Configuration
def configure_image_format(cam, pixel_format='Mono8'):
"""Configure image format settings"""
try:
nodemap = cam.GetNodeMap()
# Set pixel format
node_pixel_format = PySpin.CEnumerationPtr(nodemap.GetNode('PixelFormat'))
if PySpin.IsReadable(node_pixel_format) and PySpin.IsWritable(node_pixel_format):
# Get desired pixel format entry
node_pixel_format_entry = PySpin.CEnumEntryPtr(node_pixel_format.GetEntryByName(pixel_format))
if PySpin.IsReadable(node_pixel_format_entry):
pixel_format_value = node_pixel_format_entry.GetValue()
node_pixel_format.SetIntValue(pixel_format_value)
print(f'Pixel format set to: {pixel_format}')
else:
print(f'Pixel format {pixel_format} not available')
# Configure image dimensions
configure_image_dimensions(nodemap)
return True
except PySpin.SpinnakerException as ex:
print(f'Error configuring image format: {ex}')
return False
def configure_image_dimensions(nodemap):
"""Set image width, height, and offsets"""
try:
# Set offset X to minimum
node_offset_x = PySpin.CIntegerPtr(nodemap.GetNode('OffsetX'))
if PySpin.IsReadable(node_offset_x) and PySpin.IsWritable(node_offset_x):
node_offset_x.SetValue(node_offset_x.GetMin())
print(f'Offset X set to: {node_offset_x.GetValue()}')
# Set offset Y to minimum
node_offset_y = PySpin.CIntegerPtr(nodemap.GetNode('OffsetY'))
if PySpin.IsReadable(node_offset_y) and PySpin.IsWritable(node_offset_y):
node_offset_y.SetValue(node_offset_y.GetMin())
print(f'Offset Y set to: {node_offset_y.GetValue()}')
# Set width to maximum
node_width = PySpin.CIntegerPtr(nodemap.GetNode('Width'))
if PySpin.IsReadable(node_width) and PySpin.IsWritable(node_width):
node_width.SetValue(node_width.GetMax())
print(f'Width set to: {node_width.GetValue()}')
# Set height to maximum
node_height = PySpin.CIntegerPtr(nodemap.GetNode('Height'))
if PySpin.IsReadable(node_height) and PySpin.IsWritable(node_height):
node_height.SetValue(node_height.GetMax())
print(f'Height set to: {node_height.GetValue()}')
except PySpin.SpinnakerException as ex:
print(f'Error configuring image dimensions: {ex}')
Exposure Control
def configure_exposure(cam, exposure_time_us=10000, auto_exposure=False):
"""Configure camera exposure settings"""
try:
nodemap = cam.GetNodeMap()
# Set exposure auto mode
node_exposure_auto = PySpin.CEnumerationPtr(nodemap.GetNode('ExposureAuto'))
if PySpin.IsReadable(node_exposure_auto) and PySpin.IsWritable(node_exposure_auto):
if auto_exposure:
node_exposure_auto_entry = node_exposure_auto.GetEntryByName('Continuous')
print('Auto exposure enabled')
else:
node_exposure_auto_entry = node_exposure_auto.GetEntryByName('Off')
print('Auto exposure disabled')
if PySpin.IsReadable(node_exposure_auto_entry):
exposure_auto_value = node_exposure_auto_entry.GetValue()
node_exposure_auto.SetIntValue(exposure_auto_value)
# Set manual exposure time if auto exposure is off
if not auto_exposure:
node_exposure_time = PySpin.CFloatPtr(nodemap.GetNode('ExposureTime'))
if PySpin.IsReadable(node_exposure_time) and PySpin.IsWritable(node_exposure_time):
# Ensure exposure time is within valid range
exposure_time_to_set = min(node_exposure_time.GetMax(), exposure_time_us)
exposure_time_to_set = max(node_exposure_time.GetMin(), exposure_time_to_set)
node_exposure_time.SetValue(exposure_time_to_set)
print(f'Exposure time set to: {exposure_time_to_set} μs')
return True
except PySpin.SpinnakerException as ex:
print(f'Error configuring exposure: {ex}')
return False
Gain Control
def configure_gain(cam, gain_db=0, auto_gain=False):
"""Configure camera gain settings"""
try:
nodemap = cam.GetNodeMap()
# Set gain auto mode
node_gain_auto = PySpin.CEnumerationPtr(nodemap.GetNode('GainAuto'))
if PySpin.IsReadable(node_gain_auto) and PySpin.IsWritable(node_gain_auto):
if auto_gain:
node_gain_auto_entry = node_gain_auto.GetEntryByName('Continuous')
print('Auto gain enabled')
else:
node_gain_auto_entry = node_gain_auto.GetEntryByName('Off')
print('Auto gain disabled')
if PySpin.IsReadable(node_gain_auto_entry):
gain_auto_value = node_gain_auto_entry.GetValue()
node_gain_auto.SetIntValue(gain_auto_value)
# Set manual gain if auto gain is off
if not auto_gain:
node_gain = PySpin.CFloatPtr(nodemap.GetNode('Gain'))
if PySpin.IsReadable(node_gain) and PySpin.IsWritable(node_gain):
# Ensure gain is within valid range
gain_to_set = min(node_gain.GetMax(), gain_db)
gain_to_set = max(node_gain.GetMin(), gain_to_set)
node_gain.SetValue(gain_to_set)
print(f'Gain set to: {gain_to_set} dB')
return True
except PySpin.SpinnakerException as ex:
print(f'Error configuring gain: {ex}')
return False
Image Acquisition
Single Image Capture
def capture_single_image(cam, filename_prefix='image'):
"""Capture a single image from the camera"""
try:
# Begin acquisition
cam.BeginAcquisition()
print('Capturing image...')
# Get device serial number for filename
nodemap_tldevice = cam.GetTLDeviceNodeMap()
node_device_serial = PySpin.CStringPtr(nodemap_tldevice.GetNode('DeviceSerialNumber'))
if PySpin.IsReadable(node_device_serial):
device_serial = node_device_serial.GetValue()
else:
device_serial = 'unknown'
# Retrieve next received image
image_result = cam.GetNextImage(1000) # 1000ms timeout
# Ensure image completion
if image_result.IsIncomplete():
print(f'Image incomplete with image status {image_result.GetImageStatus()}')
else:
# Get image data
width = image_result.GetWidth()
height = image_result.GetHeight()
print(f'Captured image: {width} x {height}')
# Convert image to desired format
if image_result.GetPixelFormat() == PySpin.PixelFormat_Mono8:
image_converted = image_result
else:
image_converted = image_result.Convert(PySpin.PixelFormat_Mono8, PySpin.HQ_LINEAR)
# Create filename
filename = f'{filename_prefix}_{device_serial}.jpg'
# Save image
image_converted.Save(filename)
print(f'Image saved as: {filename}')
# Get image as numpy array (optional)
image_data = image_converted.GetNDArray()
print(f'Image data shape: {image_data.shape}')
# Release image
image_result.Release()
# End acquisition
cam.EndAcquisition()
return True
except PySpin.SpinnakerException as ex:
print(f'Error during image capture: {ex}')
return False
Multiple Image Capture
def capture_multiple_images(cam, num_images=10, filename_prefix='image'):
"""Capture multiple images from the camera"""
try:
# Begin acquisition
cam.BeginAcquisition()
print(f'Capturing {num_images} images...')
# Get device serial number for filename
nodemap_tldevice = cam.GetTLDeviceNodeMap()
node_device_serial = PySpin.CStringPtr(nodemap_tldevice.GetNode('DeviceSerialNumber'))
if PySpin.IsReadable(node_device_serial):
device_serial = node_device_serial.GetValue()
else:
device_serial = 'unknown'
# Capture images
for i in range(num_images):
# Retrieve next received image
image_result = cam.GetNextImage(1000)
# Ensure image completion
if image_result.IsIncomplete():
print(f'Image {i} incomplete with status {image_result.GetImageStatus()}')
else:
# Convert image to desired format
if image_result.GetPixelFormat() == PySpin.PixelFormat_Mono8:
image_converted = image_result
else:
image_converted = image_result.Convert(PySpin.PixelFormat_Mono8, PySpin.HQ_LINEAR)
# Create filename
filename = f'{filename_prefix}_{device_serial}_{i:03d}.jpg'
# Save image
image_converted.Save(filename)
print(f'Image {i} saved as: {filename}')
# Release image
image_result.Release()
# End acquisition
cam.EndAcquisition()
return True
except PySpin.SpinnakerException as ex:
print(f'Error during multiple image capture: {ex}')
return False
Advanced Configuration
Trigger Configuration
def configure_trigger(cam, trigger_mode='Off', trigger_source='Software'):
"""Configure camera trigger settings"""
try:
nodemap = cam.GetNodeMap()
# Set trigger mode
node_trigger_mode = PySpin.CEnumerationPtr(nodemap.GetNode('TriggerMode'))
if PySpin.IsReadable(node_trigger_mode) and PySpin.IsWritable(node_trigger_mode):
node_trigger_mode_entry = node_trigger_mode.GetEntryByName(trigger_mode)
if PySpin.IsReadable(node_trigger_mode_entry):
trigger_mode_value = node_trigger_mode_entry.GetValue()
node_trigger_mode.SetIntValue(trigger_mode_value)
print(f'Trigger mode set to: {trigger_mode}')
# Set trigger source if trigger mode is enabled
if trigger_mode == 'On':
node_trigger_source = PySpin.CEnumerationPtr(nodemap.GetNode('TriggerSource'))
if PySpin.IsReadable(node_trigger_source) and PySpin.IsWritable(node_trigger_source):
node_trigger_source_entry = node_trigger_source.GetEntryByName(trigger_source)
if PySpin.IsReadable(node_trigger_source_entry):
trigger_source_value = node_trigger_source_entry.GetValue()
node_trigger_source.SetIntValue(trigger_source_value)
print(f'Trigger source set to: {trigger_source}')
return True
except PySpin.SpinnakerException as ex:
print(f'Error configuring trigger: {ex}')
return False
def execute_software_trigger(cam):
"""Execute a software trigger"""
try:
nodemap = cam.GetNodeMap()
# Execute software trigger
node_software_trigger = PySpin.CCommandPtr(nodemap.GetNode('TriggerSoftware'))
if PySpin.IsWritable(node_software_trigger):
node_software_trigger.Execute()
print('Software trigger executed')
return True
else:
print('Software trigger not available')
return False
except PySpin.SpinnakerException as ex:
print(f'Error executing software trigger: {ex}')
return False
Buffer Handling
def configure_buffer_handling(cam, buffer_count=10, buffer_mode='NewestOnly'):
"""Configure image buffer handling"""
try:
# Get transport layer stream nodemap
nodemap_tlstream = cam.GetTLStreamNodeMap()
# Set buffer count
node_buffer_count = PySpin.CIntegerPtr(nodemap_tlstream.GetNode('StreamDefaultBufferCount'))
if PySpin.IsReadable(node_buffer_count) and PySpin.IsWritable(node_buffer_count):
node_buffer_count.SetValue(buffer_count)
print(f'Buffer count set to: {buffer_count}')
# Set buffer handling mode
node_buffer_mode = PySpin.CEnumerationPtr(nodemap_tlstream.GetNode('StreamBufferHandlingMode'))
if PySpin.IsReadable(node_buffer_mode) and PySpin.IsWritable(node_buffer_mode):
node_buffer_mode_entry = node_buffer_mode.GetEntryByName(buffer_mode)
if PySpin.IsReadable(node_buffer_mode_entry):
buffer_mode_value = node_buffer_mode_entry.GetValue()
node_buffer_mode.SetIntValue(buffer_mode_value)
print(f'Buffer handling mode set to: {buffer_mode}')
return True
except PySpin.SpinnakerException as ex:
print(f'Error configuring buffer handling: {ex}')
return False
Error Handling
Comprehensive Error Handling
def safe_camera_operation(cam, operation_func, *args, **kwargs):
"""Safely execute camera operations with error handling"""
try:
return operation_func(cam, *args, **kwargs)
except PySpin.SpinnakerException as ex:
print(f'Spinnaker error: {ex}')
return False
except Exception as ex:
print(f'General error: {ex}')
return False
Best Practices
1. Resource Management
def camera_context_manager(system, camera_index=0):
"""Context manager for proper camera resource management"""
cam_list = system.GetCameras()
if cam_list.GetSize() <= camera_index:
raise ValueError(f'Camera index {camera_index} not available')
cam = cam_list.GetByIndex(camera_index)
try:
# Initialize camera
cam.Init()
yield cam
finally:
# Clean up
cam.DeInit()
del cam
cam_list.Clear()
2. Configuration Validation
def validate_camera_settings(cam):
"""Validate camera settings before acquisition"""
try:
nodemap = cam.GetNodeMap()
# Check critical settings
checks = []
# Check acquisition mode
node_acq_mode = PySpin.CEnumerationPtr(nodemap.GetNode('AcquisitionMode'))
if PySpin.IsReadable(node_acq_mode):
checks.append(f"Acquisition Mode: {node_acq_mode.GetCurrentEntry().GetSymbolic()}")
# Check pixel format
node_pixel_format = PySpin.CEnumerationPtr(nodemap.GetNode('PixelFormat'))
if PySpin.IsReadable(node_pixel_format):
checks.append(f"Pixel Format: {node_pixel_format.GetCurrentEntry().GetSymbolic()}")
# Check image dimensions
node_width = PySpin.CIntegerPtr(nodemap.GetNode('Width'))
node_height = PySpin.CIntegerPtr(nodemap.GetNode('Height'))
if PySpin.IsReadable(node_width) and PySpin.IsReadable(node_height):
checks.append(f"Image Size: {node_width.GetValue()} x {node_height.GetValue()}")
print("Camera Settings Validation:")
for check in checks:
print(f" {check}")
return True
except PySpin.SpinnakerException as ex:
print(f'Error validating settings: {ex}')
return False
Complete Example
import PySpin
import sys
import os
from datetime import datetime
def complete_capture_example():
"""Complete example of area scan camera image capture"""
# Initialize system
system = PySpin.System.GetInstance()
try:
# Get camera list
cam_list = system.GetCameras()
num_cameras = cam_list.GetSize()
if num_cameras == 0:
print('No cameras detected!')
return False
print(f'Found {num_cameras} camera(s)')
# Get first camera
cam = cam_list.GetByIndex(0)
# Initialize camera
cam.Init()
# Print camera information
print_camera_info(cam)
# Configure camera settings
configure_stream_mode(cam, 'Socket')
configure_acquisition_mode(cam, 'Continuous')
configure_image_format(cam, 'Mono8')
configure_exposure(cam, exposure_time_us=10000, auto_exposure=False)
configure_gain(cam, gain_db=0, auto_gain=False)
configure_buffer_handling(cam, buffer_count=10, buffer_mode='NewestOnly')
# Validate settings
validate_camera_settings(cam)
# Capture images
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename_prefix = f'area_scan_capture_{timestamp}'
success = capture_multiple_images(cam, num_images=5, filename_prefix=filename_prefix)
if success:
print('Image capture completed successfully!')
else:
print('Image capture failed!')
# Clean up
cam.DeInit()
del cam
cam_list.Clear()
return success
except PySpin.SpinnakerException as ex:
print(f'Error: {ex}')
return False
except Exception as ex:
print(f'Unexpected error: {ex}')
return False
finally:
# Release system
system.ReleaseInstance()
if __name__ == '__main__':
complete_capture_example()
Key Settings Summary
Critical Settings for Area Scan Cameras:
- Acquisition Mode: Continuous for multiple images, SingleFrame for single capture
- Pixel Format: Mono8 (8-bit grayscale), RGB8 (8-bit color), Mono16 (16-bit grayscale)
- Image Dimensions: Width, Height, OffsetX, OffsetY
- Exposure Settings: ExposureAuto (Off/Continuous), ExposureTime (microseconds)
- Gain Settings: GainAuto (Off/Continuous), Gain (dB)
- Trigger Settings: TriggerMode (Off/On), TriggerSource (Software/Hardware)
- Buffer Handling: StreamDefaultBufferCount, StreamBufferHandlingMode
- Stream Mode: Socket (Linux/Mac), TeledyneGigeVision (Windows GigE)
Performance Optimization:
- Use appropriate buffer count (typically 5-10 buffers)
- Configure stream mode based on OS and camera interface
- Set buffer handling mode to “NewestOnly” for real-time applications
- Minimize exposure time for high-speed capture
- Use hardware triggering for synchronized capture
This guide provides a comprehensive foundation for working with PySpin and area scan cameras, covering everything from basic setup to advanced configuration and error handling.