Skip to content

StereoAcquisition


# 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.
# =============================================================================
#
# StereoAcquisition.cpp shows how to acquire image sets from a stereo
# camera. The image sets are then saved to file and/or used to compute 3D
# point cloud and saved as a PLY (Polygon File Format) file.
#
# This example touches on the preparation and cleanup of a camera just before
# and just after the acquisition of images. Image retrieval and conversion,
# grabbing image data, and saving images are all covered as well.
#
# 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
import platform

def set_selector_to_value(nodemap, selector, value):
    """
    Set a selector node to a specified value in a node map.

    :param nodemap: The node map from the camera.
    :param selector: The name of the selector node.
    :param value: The value to set for the selector.
    :type nodemap: INodeMap
    :type selector: string
    :type value: string
    :return: True if the selector is successfully set, False otherwise.
    :rtype: bool
    """
    try:
        selector_node = PySpin.CEnumerationPtr(nodemap.GetNode(selector))
        selector_entry = selector_node.GetEntryByName(value)
        selector_value = selector_entry.GetValue()
        selector_node.SetIntValue(selector_value)
        return True
    except PySpin.SpinnakerException:
        print("Failed to set {} selector to {} value".format(selector, value))
        return False

def enable_node(nodemap, node_name):
    """
    Enable a checkbox or boolean node in SpinView.

    :param nodemap: The node map from the camera.
    :param node_name: The name of the node to enable.
    :type nodemap: INodeMap
    :type node_name: string
    :return: True if the node was successfully enabled, False otherwise.
    :rtype: bool
    """
    try:
        the_node = PySpin.CBooleanPtr(nodemap.GetNode(node_name))
        if not PySpin.IsAvailable(the_node):
            print(f"Node '{node_name}' is not available.")
            return False

        if the_node.GetValue():
            return True
        elif PySpin.IsWritable(the_node):
            the_node.SetValue(True)
            return True
        else:
            print(f"Node '{node_name}' is not writable.")
            return False
    except PySpin.SpinnakerException as ex:
        print(f"Failed to enable node '{node_name}': {ex}")
        return False

def get_node_value(nodemap, node_name, data_type='float'):
    """
    Retrieve a node value from the camera's nodemap.

    :param nodemap: Nodemap object for the camera.
    :param node_name: The name of the node to retrieve.
    :param data_type: The expected data type of the node's value.
                         Supported values are 'float', 'int', 'bool'.
    :type nodemap: INodeMap
    :type node_name: string
    :type data_type: string 
    :return: The value of the node if successful, otherwise None.
    :rtype: float|int|bool|None
    """
    node = PySpin.CFloatPtr(nodemap.GetNode(node_name)) if data_type == 'float' else (
           PySpin.CIntegerPtr(nodemap.GetNode(node_name)) if data_type == 'int' else
           PySpin.CBooleanPtr(nodemap.GetNode(node_name)))
    if not PySpin.IsAvailable(node) or not PySpin.IsReadable(node):
        print(f"Node {node_name} not available or not readable.")
        return None
    if data_type == 'float':
        return node.GetValue()
    elif data_type == 'int':
        return node.GetValue()
    elif data_type == 'bool':
        return node.GetValue()
    else:
        print(f"Unsupported data type for node {node_name}.")
        return None

def configure_stereo_params(nodemap, scan3d_params):
    """
    Configure Scan3D parameters by reading corresponding nodes from the nodemap.

    :param nodemap: Nodemap object from which to read parameters.
    :param scan3d_params: The Scan3D parameters object to fill.
    :type nodemap: INodeMap
    :type scan3d_params: Scan3D
    """
    coord_offset = get_node_value(nodemap, 'Scan3dCoordinateOffset', 'float')
    if coord_offset is not None:
        scan3d_params.SetCoordinateOffset(coord_offset)
    else:
        print("Failed to retrieve Scan3dCoordinateOffset.")

    baseline = get_node_value(nodemap, 'Scan3dBaseline', 'float')
    if baseline is not None:
        scan3d_params.SetBaseline(baseline)
    else:
        print("Failed to retrieve Scan3DBaseline.")

    focalLength = get_node_value(nodemap, 'Scan3dFocalLength', 'float')
    if focalLength is not None:
        scan3d_params.SetFocalLength(focalLength)
    else:
        print("Failed to retrieve Scan3dFocalLength.")

    principal_point_u = get_node_value(nodemap, 'Scan3dPrincipalPointU', 'float')
    if principal_point_u is not None:
        scan3d_params.SetPrincipalPointU(principal_point_u)
    else:
        print("Failed to retrieve Scan3dPrincipalPointU.")

    principal_point_v = get_node_value(nodemap, 'Scan3dPrincipalPointV', 'float')
    if principal_point_v is not None:
        scan3d_params.SetPrincipalPointV(principal_point_v)
    else:
        print("Failed to retrieve Scan3dPrincipalPointV.")

    disparity_scale_factor = get_node_value(nodemap, 'Scan3dCoordinateScale', 'float')
    if disparity_scale_factor is not None:
        scan3d_params.SetCoordinateScale(disparity_scale_factor)
    else:
        print("Failed to retrieve Scan3dCoordinateScale.")

    invalid_data_value = get_node_value(nodemap, 'Scan3dInvalidDataValue', 'float')
    if invalid_data_value is not None:
        scan3d_params.SetInvalidDataValue(invalid_data_value)
    else:
        print("Failed to retrieve Scan3dInvalidDataValue.")

    invalid_data_flag = get_node_value(nodemap, 'Scan3dInvalidDataFlag', 'bool')
    if invalid_data_flag is not None:
        scan3d_params.SetInvalidDataFlag(invalid_data_flag)
    else:
        print("Failed to retrieve Scan3dInvalidDataFlag.")

def configure_chunk_data(nodemap):
    """
    This function configures the camera to add chunk data to each image. It does
    this by enabling each type of chunk data before enabling chunk data mode.
    When chunk data is turned on, the data is made available in both the nodemap
    and each image.

    :param nodemap: Transport layer device nodemap.
    :type nodemap: INodeMap
    :return: True if successful, False otherwise
    :rtype: bool
    """
    try:
        result = True
        print('\n*** CONFIGURING CHUNK DATA ***\n')

        # Activate chunk mode
        #
        # *** NOTES ***
        # Once enabled, chunk data will be available at the end of the payload
        # of every image captured until it is disabled. Chunk data can also be
        # retrieved from the nodemap.
        chunk_mode_active = PySpin.CBooleanPtr(nodemap.GetNode('ChunkModeActive'))

        if PySpin.IsWritable(chunk_mode_active):
            chunk_mode_active.SetValue(True)
        else:
            print('Failed to activate chunk mode...')
            return False

        print('Chunk mode activated...')

        # Enable all types of chunk data
        #
        # *** NOTES ***
        # Enabling chunk data requires working with nodes: "ChunkSelector"
        # is an enumeration selector node and "ChunkEnable" is a boolean. It
        # requires retrieving the selector node (which is of enumeration node
        # type), selecting the entry of the chunk data to be enabled, retrieving
        # the corresponding boolean, and setting it to be true.
        #
        # In this example, all chunk data is enabled, so these steps are
        # performed in a loop. Once this is complete, chunk mode still needs to
        # be activated.
        chunk_selector = PySpin.CEnumerationPtr(nodemap.GetNode('ChunkSelector'))

        if not PySpin.IsReadable(chunk_selector) or not PySpin.IsWritable(chunk_selector):
            print('Unable to retrieve chunk selector. Aborting...\n')
            return False

        # Retrieve entries
        #
        # *** NOTES ***
        # PySpin handles mass entry retrieval in a different way than the C++
        # API. Instead of taking in a NodeList_t reference, GetEntries() takes
        # no parameters and gives us a list of INodes. Since we want these INodes
        # to be of type CEnumEntryPtr, we can use a list comprehension to
        # transform all of our collected INodes into CEnumEntryPtrs at once.
        entries = [PySpin.CEnumEntryPtr(chunk_selector_entry) for chunk_selector_entry in chunk_selector.GetEntries()]

        print('Enabling entries...')

        # Iterate through our list and select each entry node to enable
        for chunk_selector_entry in entries:
            # Go to next node if problem occurs
            if not PySpin.IsReadable(chunk_selector_entry):
                continue

            chunk_selector.SetIntValue(chunk_selector_entry.GetValue())

            chunk_str = '\t {}:'.format(chunk_selector_entry.GetSymbolic())

            # Retrieve corresponding boolean
            chunk_enable = PySpin.CBooleanPtr(nodemap.GetNode('ChunkEnable'))

            # Enable the boolean, thus enabling the corresponding chunk data
            if not PySpin.IsAvailable(chunk_enable):
                print('{} not available'.format(chunk_str))
                result = False
            elif chunk_enable.GetValue() is True:
                print('{} enabled'.format(chunk_str))
            elif PySpin.IsWritable(chunk_enable):
                chunk_enable.SetValue(True)
                print('{} enabled'.format(chunk_str))
            else:
                print('{} not writable'.format(chunk_str))
                result = False

    except PySpin.SpinnakerException as ex:
        print('Error: %s' % ex)
        result = False

    return result

def disable_chunk_data(nodemap):
    """
    This function disables each type of chunk data before disabling chunk data mode.

    :param nodemap: Transport layer device nodemap.
    :type nodemap: INodeMap
    :return: True if successful, False otherwise
    :rtype: bool
    """
    try:
        result = True

        # Retrieve the selector node
        chunk_selector = PySpin.CEnumerationPtr(nodemap.GetNode('ChunkSelector'))

        if not PySpin.IsReadable(chunk_selector) or not PySpin.IsWritable(chunk_selector):
            print('Unable to retrieve chunk selector. Aborting...\n')
            return False

        # Retrieve entries
        #
        # *** NOTES ***
        # PySpin handles mass entry retrieval in a different way than the C++
        # API. Instead of taking in a NodeList_t reference, GetEntries() takes
        # no parameters and gives us a list of INodes. Since we want these INodes
        # to be of type CEnumEntryPtr, we can use a list comprehension to
        # transform all of our collected INodes into CEnumEntryPtrs at once.
        entries = [PySpin.CEnumEntryPtr(chunk_selector_entry) for chunk_selector_entry in chunk_selector.GetEntries()]

        print('Disabling entries...')

        for chunk_selector_entry in entries:
            # Go to next node if problem occurs
            if not PySpin.IsReadable(chunk_selector_entry):
                continue

            chunk_selector.SetIntValue(chunk_selector_entry.GetValue())

            chunk_symbolic_form = '\t {}:'.format(chunk_selector_entry.GetSymbolic())

            # Retrieve corresponding boolean
            chunk_enable = PySpin.CBooleanPtr(nodemap.GetNode('ChunkEnable'))

            # Disable the boolean, thus disabling the corresponding chunk data
            if not PySpin.IsAvailable(chunk_enable):
                print('{} not available'.format(chunk_symbolic_form))
                result = False
            elif not chunk_enable.GetValue():
                print('{} disabled'.format(chunk_symbolic_form))
            elif PySpin.IsWritable(chunk_enable):
                chunk_enable.SetValue(False)
                print('{} disabled'.format(chunk_symbolic_form))
            else:
                print('{} not writable'.format(chunk_symbolic_form))

        # Deactivate Chunk Mode
        chunk_mode_active = PySpin.CBooleanPtr(nodemap.GetNode('ChunkModeActive'))

        if not PySpin.IsWritable(chunk_mode_active):
            print('Unable to deactivate chunk mode. Aborting...\n')
            return False

        chunk_mode_active.SetValue(False)

        print('Chunk mode deactivated...')

    except PySpin.SpinnakerException as ex:
        print('Error: %s' % ex)
        result = False

    return result

def display_chunk_data_from_image(image):
    """
    This function displays a select amount of chunk data from the image. Unlike
    accessing chunk data via the nodemap, there is no way to loop through all
    available data.

    :param image: Image to acquire chunk data from
    :type image: Image object
    :return: True if successful, False otherwise.
    :rtype: bool
    """
    print('Printing chunk data from image...')
    try:
        result = True
        print(type(image))
        # Retrieve chunk data from image
        #
        # *** NOTES ***
        # When retrieving chunk data from an image, the data is stored in a
        # ChunkData object and accessed with getter functions.
        chunk_data = image.GetChunkData()

        # Retrieve frame ID
        frame_id = chunk_data.GetFrameID()
        print('\tFrame ID: {}'.format(frame_id))

        # The following are stereo-specific chunk data attributes
        # Retrieve stereo height; stereo height recorded in pixels
        stereo_height = chunk_data.GetStereoHeight()
        print('\tStereo Height: {}'.format(stereo_height))

        # Retrieve stereo width; stereo width recorded in pixels
        stereo_width = chunk_data.GetStereoWidth()
        print('\tStereo Width: {}'.format(stereo_width))

        # Retrieve Scan3dCoordinateOffset; Scan3dCoordinateOffset recorded in pixels
        scan3d_coordinate_offset = chunk_data.GetScan3dCoordinateOffset()
        print('\tScan3dCoordinateOffset: {}'.format(scan3d_coordinate_offset))

        # Retrieve Scan3dBaseline; Scan3dBaseline recorded in meters
        scan3d_baseline = chunk_data.GetScan3dBaseline()
        print('\tScan3dBaseline: {}'.format(scan3d_baseline))

        # Retrieve Scan3dFocalLength; Scan3dFocalLength recorded in pixels
        scan3d_focal_length = chunk_data.GetScan3dFocalLength()
        print('\tScan3dFocalLength: {}'.format(scan3d_focal_length))

        # Retrieve MinZ; MinZ recorded in meters
        min_z = chunk_data.GetMinZ()
        print('\tMinZ: {}'.format(min_z))

        # Retrieve MaxZ; MaxZ recorded in meters
        max_z = chunk_data.GetMaxZ()
        print('\tMaxZ: {}'.format(max_z))

        # Retrieve Scan3dPrincipalPointU; Scan3dPrincipalPointU recorded in pixels
        scan3d_principal_point_u = chunk_data.GetScan3dPrincipalPointU()
        print('\tScan3dPrincipalPointU: {}'.format(scan3d_principal_point_u))

        # Retrieve Scan3dPrincipalPointV; Scan3dPrincipalPointV recorded in pixels
        scan3d_principal_point_v = chunk_data.GetScan3dPrincipalPointV()
        print('\tScan3dPrincipalPointV: {}'.format(scan3d_principal_point_v))

        # Retrieve SmallPenalty; SmallPenalty recorded in pixels
        small_penalty = chunk_data.GetSmallPenalty()
        print('\tSmallPenalty: {}'.format(small_penalty))

        # Retrieve LargePenalty; LargePenalty recorded in pixels
        large_penalty = chunk_data.GetLargePenalty()
        print('\tLargePenalty: {}'.format(large_penalty))

        #Retrieve UniquenessRatio; UniquenessRatio recorded in percentage
        uniqueness_ratio = chunk_data.GetUniquenessRatio()
        print('\tUniquenessRatio: {}'.format(uniqueness_ratio))

    except PySpin.SpinnakerException as ex:
        print('Error: %s' % ex)
        result = False
    return result

def compute_3d_point_cloud_and_save(scan3d_params, image_list, filename_prefix, i):
    """
    This function computes a point cloud from a disparity image,
    and saves the point cloud into a .ply file

    :param scan3d_params: the Scan3D parameters.
    :param image_list: list of acquired streams.
    :param filename_prefix: file name prefix.
    :param i: local index of the image list.
    :type scan3d_params: Scan3D
    :type image_list: ImageList
    :type filename_prefix: string
    :type i: int
    :return: True if successful, False otherwise.
    :rtype: bool
    """

    point_cloud_params = PySpin.PointCloudParameters()

    point_cloud_params.decimationFactor = 1
    point_cloud_params.ROIImageLeft = 0
    point_cloud_params.ROIImageTop = 0
    point_cloud_params.ROIImageRight = image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_DISPARITY_SENSOR1).GetWidth()
    point_cloud_params.ROIImageBottom = image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_DISPARITY_SENSOR1).GetHeight()

    point_cloud = PySpin.ImageUtilityStereo.ComputePointCloud(
        image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_DISPARITY_SENSOR1),
        image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_RECTIFIED_SENSOR1),
        point_cloud_params,
        scan3d_params);

    filename = '%sPointCloud_%d.ply' % (filename_prefix, i)
    print('Save point cloud to file: %s\n' % filename)

    point_cloud.SavePointCloudAsPly(filename);

    return True;

def print_stereo_params(scan3d_params):
    """
    This function prints the Scan3D parameters.

    :param scan3d_params: the Scan3D parameters.
    :type scan3d_params: Scan3D
    :return: True if successful, False otherwise.
    :rtype: bool
    """

    print('coordinateOffset: ', scan3d_params.GetCoordinateOffset())
    print('baseline: ', scan3d_params.GetBaseline())
    print('principalPointU: ', scan3d_params.GetPrincipalPointU())
    print('principalPointV: ', scan3d_params.GetPrincipalPointV())
    print('disparityScaleFactor: ', scan3d_params.GetCoordinateScale())
    print('invalidDataValue: ', scan3d_params.GetInvalidDataValue())
    print('invalidDataFlag: ', scan3d_params.GetInvalidDataFlag())

def validate_image_list(stream_transmit_flags, image_list):
    """
    This function validates the image list.

    :param stream_transmit_flags: indicates whether the stream is transmitted.
    :param image_list: list of acquired streams.
    :type stream_transmit_flags: dict
    :type image_list: ImageList
    :return: True if successful, False otherwise.
    :rtype: bool
    """

    is_image_list_incomplete = False;

    if stream_transmit_flags['rawSensor1TransmitEnabled']:
        image = image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_RAW_SENSOR1);
        is_image_list_incomplete |= ((image is None) or image.IsIncomplete())

    if stream_transmit_flags['rawSensor2TransmitEnabled']:
        image = image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_RAW_SENSOR2);
        is_image_list_incomplete |= ((image is None) or image.IsIncomplete())

    if stream_transmit_flags['rectSensor1TransmitEnabled']:
        image = image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_RECTIFIED_SENSOR1);
        is_image_list_incomplete |= ((image is None) or image.IsIncomplete())

    if stream_transmit_flags['rectSensor2TransmitEnabled']:
        image = image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_RECTIFIED_SENSOR2);
        is_image_list_incomplete |= ((image is None) or image.IsIncomplete())

    if stream_transmit_flags['disparityTransmitEnabled']:
        image = image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_DISPARITY_SENSOR1);
        is_image_list_incomplete |= ((image is None) or image.IsIncomplete())

    if is_image_list_incomplete:
        print('Image List is incomplete.')

    return not is_image_list_incomplete

def save_images_to_files(stream_transmit_flags, image_list, filename_prefix, i):
    """
    This function saves an image list to files.

    :param stream_transmit_flags: indicates whether streams are transmitted.
    :param image_list: list of acquired streams.
    :param filename_prefix: file name prefix.
    :param i: index of the image in the list
    :type stream_transmit_flags: dict
    :type image_list: ImageList
    :type filename_prefix: string
    :type i: int
    :return: True if successful, False otherwise.
    :rtype: bool
    """

    print('Save images to files.')

    if stream_transmit_flags['rawSensor1TransmitEnabled']:
        # StereoAcquisition_24102534_RectSensor1_0.png
        filename = '%sRawSensor1_%d.png' % (filename_prefix, i)
        print('Save raw Sensor1 image to file: %s' % filename)
        image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_RAW_SENSOR1).Save(filename);

    if stream_transmit_flags['rawSensor2TransmitEnabled']:
        filename = '%sRawSensor2_%d.png' % (filename_prefix, i)
        print('Save raw Sensor2 image to file: %s' % filename)
        image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_RAW_SENSOR2).Save(filename);

    if stream_transmit_flags['rectSensor1TransmitEnabled']:
        filename = '%sRectSensor1_%d.png' % (filename_prefix, i)
        print('Save rect Sensor1 image to file: %s' % filename)
        image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_RECTIFIED_SENSOR1).Save(filename);

    if stream_transmit_flags['rectSensor2TransmitEnabled']:
        filename = '%sRectSensor2_%d.png' % (filename_prefix, i)
        print('Save rect Sensor2 image to file: %s' % filename)
        image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_RECTIFIED_SENSOR2).Save(filename);

    if stream_transmit_flags['disparityTransmitEnabled']:
        filename = '%sDisparity_%d.pgm' % (filename_prefix, i)
        print('Save disparity image to file: %s' % filename)
        image_list.GetByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_DISPARITY_SENSOR1).Save(filename);

    return True


def enable_camera_stream(cam_node_map, source, component):
    """
    This function enables a camera stream.

    :param cam_node_map: Camera node map.
    :param source: source of the stream.
    :param component: component of the stream.
    :type cam_node_map: INodeMap
    :type source: string
    :type component: string
    :return: True if successful, False otherwise.
    :rtype: bool
    """
    # Selects the source (sensor1 or sensor2)
    if not set_selector_to_value(cam_node_map, "SourceSelector", source):
        return False

    # Selects the component (raw, rectified or disparity)
    if not set_selector_to_value(cam_node_map, "ComponentSelector", component):
        return False

    # Enables the stream
    if not enable_node(cam_node_map, 'ComponentEnable'):
        return False

    return True



def acquire_images(cam, nodemap, nodemap_tldevice, device_serial_number):
    """
    This function acquires and saves multiple image sets 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

        # In order to access the node entries, they have to be casted to a pointer type (CEnumerationPtr here)
        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...')

        stream_transmit_flags = {}
        stream_transmit_flags['rawSensor1TransmitEnabled'] = False
        stream_transmit_flags['rawSensor2TransmitEnabled'] = False
        stream_transmit_flags['rectSensor1TransmitEnabled'] = True
        stream_transmit_flags['rectSensor2TransmitEnabled'] = False
        stream_transmit_flags['disparityTransmitEnabled'] = True

        # Enable required streams
        if not enable_camera_stream(nodemap, 'Sensor1', 'Rectified') \
        or not enable_camera_stream(nodemap, 'Sensor1', 'Disparity'):
            # if either stream is not enabled
            print('Unable to enable required streams. Aborting...')
            return False

        #  Begin acquiring images
        #
        #  *** NOTES ***
        #  What happens when the camera begins acquiring images depends on the
        #  acquisition mode. Single frame captures only a single image, multi
        #  frame captures a set number of images, and continuous captures a
        #  continuous stream of images. Because the example calls for the
        #  retrieval of 3 images, continuous mode has been set.
        #
        #  *** LATER ***
        #  Image acquisition must be ended when no more images are needed.

        cam.BeginAcquisition()
        print('Acquiring images...')


        postProcessDisparity = True
        doComputePointCloud = True

        scan3d_params = PySpin.Scan3DParameters()
        configure_stereo_params(nodemap, scan3d_params)

        print('*** STEREO PARAMETERS *** ')
        print_stereo_params(scan3d_params)

        timeoutInMilliSecs = 2000;
        num_images_sets = 3
        print('\nAcquiring %d image sets.\n' % num_images_sets)

        # Retrieve, process the image sets
        for i in range(num_images_sets):
            try:
                print('Acquiring stereo image set: %d' % i)

                image_list = cam.GetNextImageSync(timeoutInMilliSecs)

                if not validate_image_list(stream_transmit_flags, image_list):
                    print('Failed to get next image set.')

                # Chunk Data is only available from the last Image in the ImageList for multi-stream cameras
                img = image_list.GetByIndex(image_list.GetSize() - 1)
                display_chunk_data_from_image(img)

                processed_disparity = False
                for j, image in enumerate(image_list):
                    if image.GetImagePayloadType() == PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_DISPARITY_SENSOR1:
                        if postProcessDisparity:
                            disparity_img = PySpin.Image.Create(image)
                            PySpin.ImageUtilityStereo.ProcessDisparityFromImage(disparity_img, 
                                                                       image, 
                                                                       PySpin.SPINNAKER_DISPARITY_PROCESSING_METHOD_HIGH_PERFORMANCE,
                                                                       scan3d_params)
                            processed_disparity = True

                if (processed_disparity):
                    image_list.RemoveByPayloadType(PySpin.SPINNAKER_IMAGE_PAYLOAD_TYPE_DISPARITY_SENSOR1)
                    image_list.Add(disparity_img) # Only safe because we are not relying on the order of images in the list 

                filename_prefix = 'StereoAcquisition_%s_' % (device_serial_number)
                if not save_images_to_files(stream_transmit_flags, image_list, filename_prefix, i):
                    print('Failed to save images.')
                    result = False
                    break

                if (doComputePointCloud and stream_transmit_flags['disparityTransmitEnabled'] and 
                    stream_transmit_flags['rectSensor1TransmitEnabled']):
                    if not compute_3d_point_cloud_and_save(scan3d_params, image_list, filename_prefix, i):
                        print('Failed to compute the 3D point cloud.')
                        result = False
                        break

            except PySpin.SpinnakerException as ex:
                print('Error: %s' % ex)
                result = False

        cam.EndAcquisition()

    except PySpin.SpinnakerException as ex:
        print('Error: %s' % ex)
        result = 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 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()

        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)

        is_stereo_camera = PySpin.ImageUtilityStereo.IsStereoCamera(cam)
        if is_stereo_camera:
            print('Camera %s is a stereo camera' % device_serial_number)
        else:
            print('Camera %s is not a valid BX camera. Skipping...' % device_serial_number)
            return True

        # Configure chunk data
        print('Configuring chunk data...')
        result &= configure_chunk_data(nodemap)

        # Acquire images
        print('Acquiring images...')
        result &= acquire_images(cam, nodemap, nodemap_tldevice, device_serial_number)

        # Disable chunk data
        print('Disabling chunk data...')
        result &= disable_chunk_data(nodemap)

        # Deinitialize camera
        cam.DeInit()

    except PySpin.SpinnakerException as ex:
        print('Error: %s' % 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
    """
    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: %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 system instance
    system.ReleaseInstance()

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

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