Industrial Camera
Industrial Camera

PySpin Area Scan Camera Image Capture Guide

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

  1. System Setup
  2. Camera Discovery and Initialization
  3. Essential Camera Settings
  4. Image Acquisition
  5. Advanced Configuration
  6. Error Handling
  7. Best Practices
  8. 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:

  1. Acquisition Mode: Continuous for multiple images, SingleFrame for single capture
  2. Pixel Format: Mono8 (8-bit grayscale), RGB8 (8-bit color), Mono16 (16-bit grayscale)
  3. Image Dimensions: Width, Height, OffsetX, OffsetY
  4. Exposure Settings: ExposureAuto (Off/Continuous), ExposureTime (microseconds)
  5. Gain Settings: GainAuto (Off/Continuous), Gain (dB)
  6. Trigger Settings: TriggerMode (Off/On), TriggerSource (Software/Hardware)
  7. Buffer Handling: StreamDefaultBufferCount, StreamBufferHandlingMode
  8. 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.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply