ImageRelay¶
ImageRelay.cpp shows how to setup relays for forwarding Spinnaker images from client PC to Host PC using the SpinnakerRelay library. It relies on information provided in the Enumeration, Acquisition, and ImageEvent examples.
//=============================================================================
// 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.
//=============================================================================
/**
* @example ImageRelay.cpp
*
* @brief ImageRelay.cpp shows how to setup relays for forwarding Spinnaker images from
* client PC to Host PC using the SpinnakerRelay library. It relies on information
* provided in the Enumeration, Acquisition, and ImageEvent examples.
*
* It can also be helpful to familiarize yourself with the ImageEvents
* example, as image callbacks follow the same general procedure as
* relayed image events, but with a few less steps.
*
* This example creates a user-defined class, ImageEventHandlerImpl, that inherits
* from the Spinnaker class, ImageEventHandler. ImageEventHandlerImpl allows the user to
* define any properties, parameters, and the event itself while ImageEventHandler
* allows the child class to appropriately interface with Spinnaker Relay class.
*
* In order to run this example, a supported RoCE connection needs to be established. For
* LR-Link Mellanox based adapters, install the WinOF-2 drivers:
* https://network.nvidia.com/products/adapter-software/ethernet/windows/winof-2/
*
* 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
*/
#include "Spinnaker.h"
#include "SpinGenApi/SpinnakerGenApi.h"
#include "SpinnakerRelay.h"
#include <chrono>
#include <iomanip>
#include <iostream>
#include <string>
#include <thread>
using namespace Spinnaker;
using namespace Spinnaker::GenApi;
using namespace Spinnaker::GenICam;
using namespace Spinnaker::Relay;
using namespace std;
// Following parameters can be configured through command-line arguments.
// Use "-h" argument to see detailed usage information.
bool bReceiver = false;
bool bSender = false;
gcstring senderIPAddress;
gcstring receiverIPAddress;
unsigned int senderPort = 8080;
unsigned int receiverPort = 8080;
bool bSourceIPSpecified = false;
ConnectParameters relayConnectParams;
unsigned int numImagesToGrab = 100;
bool saveImages = false;
uint64_t timeout = EVENT_TIMEOUT_INFINITE;
struct TimeInformation
{
const std::chrono::system_clock::time_point invalid_time = std::chrono::system_clock::time_point{};
std::chrono::system_clock::time_point startTime;
std::chrono::system_clock::time_point startTimeInstance;
std::chrono::system_clock::time_point instanceFirstEvent;
std::chrono::system_clock::time_point firstEvent;
std::chrono::system_clock::time_point instanceLastEvent;
std::chrono::system_clock::time_point lastEvent;
TimeInformation()
{
startTime = startTimeInstance = std::chrono::system_clock::now();
instanceFirstEvent = firstEvent = instanceLastEvent = lastEvent = invalid_time;
}
void RecordImageEvent(std::chrono::system_clock::time_point imageEventTime)
{
instanceFirstEvent = instanceFirstEvent == invalid_time ? imageEventTime : instanceFirstEvent;
firstEvent = firstEvent == invalid_time ? imageEventTime : firstEvent;
lastEvent = instanceLastEvent = imageEventTime;
}
void ResetTimeInstance()
{
startTimeInstance = std::chrono::system_clock::now();
instanceFirstEvent = instanceLastEvent = invalid_time;
}
};
struct ThreadParameters
{
SpinnakerRelay* relay;
bool isSender;
CameraPtr pCam;
TimeInformation* timeInfo;
ThreadParameters()
{
relay = nullptr;
isSender = false;
pCam = nullptr;
timeInfo = nullptr;
}
};
// Supported command-line arguments
const char* argPrintUsage = "-h";
const char* argStartReceiver = "-r";
const char* argStartSender = "-s";
const char* argReceiverIP = "-ri";
const char* argReceiverPort = "-rp";
const char* argSenderIP = "-si";
const char* argSenderPort = "-sp";
const char* argNumImages = "-n";
const char* argSaveImages = "-d";
const char* argTimeout = "-t";
void PrintUsage()
{
cout << "Use '" << argPrintUsage << "' to see list of supported arguments." << endl << endl;
cout << "ImageRelay [options] <ip>" << endl << endl;
cout << "Example Usage:" << endl;
cout << "\tReceiver PC (Receiving Data): ImageRelay.exe " << argStartReceiver << " " << argReceiverIP
<< " <ReceiverIP>" << endl;
cout << "\tSender PC (Sending Data): ImageRelay.exe " << argStartSender << " " << argReceiverIP << " <ReceiverIP>"
<< argSenderIP << " <SenderIP>" << endl
<< endl;
cout << "Options:" << endl;
cout << "\t" << argStartReceiver << "\t"
<< "<Starts as receiver (listen on IP:Port)>" << endl;
cout << "\t" << argStartSender << "\t"
<< "<Starts as sender (connect to receiver IP:Port)>" << endl;
cout << "\t" << argNumImages << "\t"
<< "<Optional. Number of images to transmit (default " << numImagesToGrab << ")>" << endl;
cout << "\t" << argSaveImages << "\t"
<< "<Optional. Saves grabbed images to disk>" << endl;
cout << "\t" << argTimeout << "\t"
<< "<Optional. Connection timeout in ms (default is EVENT_TIMEOUT_INFINITE for waiting indefinitely)>" << endl;
cout << "\t" << argSenderIP << "\t"
<< "<IPv4 Sender Address>" << endl;
cout << "\t" << argSenderPort << "\t"
<< "<IPv4 Sender Port (default " << senderPort << ")>" << endl;
cout << "\t" << argReceiverIP << "\t"
<< "<IPv4 Receiver Address>" << endl;
cout << "\t" << argReceiverPort << "\t"
<< "<IPv4 Receiver Port (default " << receiverPort << ")>" << endl;
}
bool ParseArguments(int argc, char* argv[])
{
cout << "*** PARSING ARGUMENTS ***" << endl << endl;
if (argc == 1)
{
PrintUsage();
return false;
}
for (int argument = 1; argument < argc; ++argument)
{
std::string arg = argv[argument];
if (arg == argPrintUsage)
{
// Print usage information
PrintUsage();
return false;
}
if (arg == argStartReceiver)
{
if (argument + 1 <= argc)
{
bReceiver = true;
}
}
if (arg == argStartSender)
{
if (argument + 1 <= argc)
{
bSender = true;
}
}
if (arg == argReceiverIP)
{
if (argument + 1 <= argc)
{
receiverIPAddress = argv[argument + 1];
argument++;
}
}
if (arg == argReceiverPort)
{
if (argument + 1 <= argc)
{
receiverPort = atoi(argv[argument + 1]);
argument++;
}
}
if (arg == argSenderIP)
{
if (argument + 1 <= argc)
{
bSourceIPSpecified = true;
senderIPAddress = argv[argument + 1];
argument++;
}
}
if (arg == argSenderPort)
{
if (argument + 1 <= argc)
{
senderPort = atoi(argv[argument + 1]);
argument++;
}
}
if (arg == argNumImages)
{
if (argument + 1 <= argc)
{
numImagesToGrab = atoi(argv[argument + 1]);
argument++;
}
}
if (arg == argSaveImages)
{
saveImages = true;
}
if (arg == argTimeout)
{
if (argument + 1 <= argc)
{
timeout = atoi(argv[argument + 1]);
argument++;
}
}
}
if ((bSender && bReceiver) || (!bSender && !bReceiver))
{
cout << "Exactly one of receiver (-r) or sender (-s) must be specified." << endl << endl;
PrintUsage();
return false;
}
if (bSender && !bSourceIPSpecified)
{
cout << "Please provide source IP address with -si <ip address>" << endl;
PrintUsage();
return false;
}
if (bSender)
{
cout << "Sender IP Address: " << senderIPAddress << endl;
cout << "Sender Port: " << senderPort << endl;
}
cout << "Receiver IP Address: " << receiverIPAddress << endl;
cout << "Receiver Port: " << receiverPort << endl;
// Setup Spinnaker Relay Connection Params
relayConnectParams.role = (bReceiver ? SPINNAKER_ROLE_RECEIVER : SPINNAKER_ROLE_SENDER);
relayConnectParams.receiverIPAddress = receiverIPAddress;
relayConnectParams.receiverPort = receiverPort;
relayConnectParams.timeout = timeout;
if (bSender)
{
relayConnectParams.senderIPAddress = senderIPAddress;
relayConnectParams.senderPort = senderPort;
}
return true;
}
void PrintRelayStatistics(const RelayStatistics* stats, TimeInformation* timeInfo, bool printSummary = false)
{
const auto currentTime = std::chrono::system_clock::now();
if (printSummary)
{
const auto totalDuration = currentTime - timeInfo->startTime;
const auto totalDurationInMicroSeconds =
std::chrono::duration_cast<std::chrono::microseconds>(totalDuration).count();
const auto totalDurationInSeconds = static_cast<double>(totalDurationInMicroSeconds) / 1000000;
const auto activeDuration = timeInfo->lastEvent - timeInfo->firstEvent;
const auto activeDurationInMicroSeconds =
std::chrono::duration_cast<std::chrono::microseconds>(activeDuration).count();
const auto activeDurationInSeconds = static_cast<double>(activeDurationInMicroSeconds) / 1000000;
bool dataTransferred = false;
if (stats->totalRecvSuccessCount > 0 || stats->totalRecvDropCount > 0)
{
std::cout << "Transfer Duration (seconds) : " << activeDurationInSeconds << std::endl;
std::cout << "Total Duration (seconds) : " << totalDurationInSeconds << std::endl;
std::cout << "Received images : " << stats->totalRecvSuccessCount << std::endl;
std::cout << "Receive images dropped : " << stats->totalRecvDropCount << std::endl;
std::cout << "Receive image errors : " << stats->totalRecvErrorCount << std::endl;
if (activeDurationInSeconds > 1)
{
double totalRecvFrameRate = stats->totalRecvSuccessCount / activeDurationInSeconds;
double totalRecvDataRate = stats->totalRecvDataCount / activeDurationInSeconds;
std::cout << "Transfer FPS : " << totalRecvFrameRate << std::endl;
std::cout << "Transfer data rate : " << totalRecvDataRate / 1024 / 1024 << " MBps"
<< std::endl;
}
dataTransferred = true;
}
if (stats->totalSendSuccessCount > 0 || stats->totalSendDropCount > 0)
{
std::cout << "Transfer Duration (seconds) : " << activeDurationInSeconds << std::endl;
std::cout << "Total Duration (seconds) : " << totalDurationInSeconds << std::endl;
std::cout << "Sent images : " << stats->totalSendSuccessCount << std::endl;
std::cout << "Send images dropped : " << stats->totalSendDropCount << std::endl;
std::cout << "Send image errors : " << stats->totalSendErrorCount << std::endl;
if (activeDurationInSeconds > 1)
{
double totalSendFrameRate = stats->totalSendSuccessCount / activeDurationInSeconds;
double totalSendDataRate = stats->totalSendDataCount / activeDurationInSeconds;
std::cout << "Transfer FPS : " << totalSendFrameRate << std::endl;
std::cout << "Transfer data rate : " << totalSendDataRate / 1024 / 1024 << " MBps"
<< std::endl;
}
dataTransferred = true;
}
if (!dataTransferred)
{
std::cout << "Duration (seconds) : " << totalDurationInSeconds << std::endl;
std::cout << "No relay data transferred.\n";
}
}
else
{
const auto transferDuration = timeInfo->instanceLastEvent - timeInfo->instanceFirstEvent;
const auto transferDurationInMicroSeconds =
std::chrono::duration_cast<std::chrono::microseconds>(transferDuration).count();
const auto transferDurationInSeconds = static_cast<double>(transferDurationInMicroSeconds) / 1000000;
bool dataTransferred = false;
if (stats->instanceRecvSuccessCount > 0 || stats->instanceRecvDropCount > 0)
{
if (transferDurationInSeconds > 1)
{
double transferRecvFrameRate = stats->instanceRecvSuccessCount / transferDurationInSeconds;
double transferRecvDataRate = stats->instanceRecvDataCount / transferDurationInSeconds;
std::cout << "Transfer FPS: " << std::fixed << std::setprecision(3) << transferRecvFrameRate << "; ";
std::cout << "Transfer data Rate: " << std::fixed << std::setprecision(3)
<< transferRecvDataRate / 1024 / 1024 << " MBps; ";
}
std::cout << "Received: " << stats->instanceRecvSuccessCount << "; ";
std::cout << "Drops: " << stats->instanceRecvDropCount << "; ";
std::cout << "Errors: " << stats->instanceRecvErrorCount << std::endl;
dataTransferred = true;
}
if (stats->instanceSendSuccessCount > 0 || stats->instanceSendDropCount > 0)
{
if (transferDurationInSeconds > 1)
{
double transferSendFrameRate = stats->instanceSendSuccessCount / transferDurationInSeconds;
double transferSendDataRate = stats->instanceSendDataCount / transferDurationInSeconds;
std::cout << "Transfer FPS: " << std::fixed << std::setprecision(3) << transferSendFrameRate << "; ";
std::cout << "Transfer data Rate: " << std::fixed << std::setprecision(3)
<< transferSendDataRate / 1024 / 1024 << " MBps; ";
}
std::cout << "Sent: " << stats->instanceSendSuccessCount << "; ";
std::cout << "Drops: " << stats->instanceSendDropCount << "; ";
std::cout << "Errors: " << stats->instanceSendErrorCount << std::endl;
dataTransferred = true;
}
if (!dataTransferred)
{
std::cout << "No relay data transferred.\n";
}
}
}
// This class defines the properties, parameters, and the event handler itself. Take a
// moment to notice what parts of the class are mandatory, and what have been
// added for demonstration purposes. First, any class used to define image event handlers
// must inherit from ImageEventHandler. Second, the method signature of OnImageEvent()
// must also be consistent. Everything else - including the constructor,
// destructor, properties, body of OnImageEvent(), and other functions - is particular
// to the example.
class ImageEventHandlerImpl : public ImageEventHandler
{
public:
ImageEventHandlerImpl(TimeInformation* timeInformation, unsigned int maxImages, bool saveImagesToDisk = false)
{
// Initialize image counter to 0
m_imageCnt = 0;
// Initialize max image to grab
m_numImages = maxImages;
// Set flag for saving grabbed images locally
m_saveImages = saveImagesToDisk;
// Reference TimeInformation struct
timeInfo = timeInformation;
}
// The constructor retrieves the serial number and initializes the image
// counter to 0.
ImageEventHandlerImpl(
CameraPtr pCam,
TimeInformation* timeInformation,
unsigned int maxImages,
bool saveImagesToDisk = false)
{
// Retrieve device serial number
INodeMap& nodeMap = pCam->GetTLDeviceNodeMap();
m_deviceSerialNumber = "";
CStringPtr ptrDeviceSerialNumber = nodeMap.GetNode("DeviceSerialNumber");
if (IsReadable(ptrDeviceSerialNumber))
{
m_deviceSerialNumber = ptrDeviceSerialNumber->GetValue();
}
// Initialize image counter to 0
m_imageCnt = 0;
// Initialize max image to grab
m_numImages = maxImages;
// Set flag for saving grabbed images locally
m_saveImages = saveImagesToDisk;
// Reference TimeInformation struct
timeInfo = timeInformation;
// Release reference to camera
pCam = nullptr;
//
// 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.
//
m_processor.SetColorProcessing(SPINNAKER_COLOR_PROCESSING_ALGORITHM_HQ_LINEAR);
}
~ImageEventHandlerImpl()
{
}
// This method defines an image event. In it, the image that triggered the
// event is converted and saved before incrementing the count. Please see
// Acquisition example for more in-depth comments on the acquisition of
// images.
void OnImageEvent(ImagePtr image)
{
const auto imageEventTime = std::chrono::system_clock::now();
// Save a maximum of m_numImages images
if (m_imageCnt < m_numImages)
{
// Check image retrieval status
if (image->IsIncomplete())
{
cout << "Image incomplete with image status " << image->GetImageStatus() << "..." << endl << endl;
}
else
{
// Print image information
// Print the first 10 frames; afterwards, print once for every 200 frames to avoid flooding the
// screen with debug messages.
const uint64_t frameID = image->GetFrameID();
if (frameID < 10 || frameID % 200 == 0)
{
cout << "Grabbed image " << m_imageCnt << ", id = " << frameID << ", width = " << image->GetWidth()
<< ", height = " << image->GetHeight() << endl;
}
if (m_saveImages)
{
// Create a unique filename and save image
ostringstream filename;
filename << "ImageRelay-";
if (m_deviceSerialNumber != "")
{
filename << m_deviceSerialNumber.c_str() << "-";
}
// Save in .si format to retain image metadata
filename << m_imageCnt << ".si";
image->Save(filename.str().c_str());
cout << "Image saved at " << filename.str() << endl << endl;
}
// Increment image counter
m_imageCnt++;
timeInfo->RecordImageEvent(imageEventTime);
}
}
}
// Getter for image counter
int getImageCount()
{
return m_imageCnt;
}
// Getter for maximum images
int getMaxImages()
{
return m_numImages;
}
// Setter for maximum images
int setMaxImages(unsigned int maxImages)
{
m_numImages = maxImages;
return m_numImages;
}
private:
unsigned int m_numImages = 10;
unsigned int m_imageCnt;
bool m_saveImages = false;
string m_deviceSerialNumber;
ImageProcessor m_processor;
TimeInformation* timeInfo;
};
// This function configures the example to execute image events by preparing and
// registering an image event for specified SpinnakerRelay.
int ConfigureImageEvents(
SpinnakerRelay& relay,
ImageEventHandlerImpl*& imageEventHandler,
TimeInformation* timeInformation,
bool saveImagesToDisk)
{
int result = 0;
try
{
//
// Create image event handler
//
imageEventHandler = new ImageEventHandlerImpl(timeInformation, numImagesToGrab, saveImagesToDisk);
//
// Register image event handler
//
// *** LATER ***
// Image event handlers must be unregistered manually. This must be done prior
// to releasing the system and while the image event handlers are still in
// scope.
//
relay.RegisterEventHandler(*imageEventHandler);
}
catch (Spinnaker::Exception& e)
{
cout << "Error: " << e.what() << endl;
result = -1;
}
return result;
}
// This function configures the example to execute image events by preparing and
// registering an image event for specified Camera.
int ConfigureImageEvents(
CameraPtr pCam,
ImageEventHandlerImpl*& imageEventHandler,
TimeInformation* timeInformation,
bool saveImagesToDisk)
{
int result = 0;
try
{
//
// Create image event handler
//
// *** NOTES ***
// The class has been constructed to accept a camera pointer in order
// to allow the saving of images with the device serial number.
//
imageEventHandler = new ImageEventHandlerImpl(pCam, timeInformation, numImagesToGrab, saveImagesToDisk);
//
// Register image event handler
//
// *** NOTES ***
// Image event handlers are registered to cameras. If there are multiple
// cameras, each camera must have the image event handlers registered to it
// separately. Also, multiple image event handlers may be registered to a
// single camera.
//
// *** LATER ***
// Image event handlers must be unregistered manually. This must be done prior
// to releasing the system and while the image event handlers are still in
// scope.
//
pCam->RegisterEventHandler(*imageEventHandler);
}
catch (Spinnaker::Exception& e)
{
cout << "Error: " << e.what() << endl;
result = -1;
}
return result;
}
// This function waits for the appropriate amount of images. Notice that
// whereas most examples actively retrieve images, the acquisition of images is
// handled passively in this example.
int WaitForImages(ImageEventHandlerImpl*& imageEventHandler, SpinnakerRelay& relay)
{
int result = 0;
try
{
//
// Wait for images
//
// *** NOTES ***
// In order to passively capture images using image events and
// automatic polling, the main thread sleeps in increments of 1000 ms
// until the requested number of images have been acquired and saved.
//
const int sleepDuration = 1000; // in milliseconds
bool printConnectingMsg = true;
while (imageEventHandler->getImageCount() < imageEventHandler->getMaxImages())
{
Sleep(sleepDuration);
if (!relay.IsConnected())
{
if (relay.IsConnecting())
{
if (printConnectingMsg)
{
cout << "Relay is connecting. Please wait..." << endl;
printConnectingMsg = false;
}
}
else
{
cout << "Relay is disconnected." << endl;
break;
}
}
else
{
printConnectingMsg = true;
}
}
}
catch (Spinnaker::Exception& e)
{
cout << "Error: " << e.what() << endl;
result = -1;
}
return result;
}
// This functions resets the example by unregistering the image event handler.
int ResetImageEvents(SpinnakerRelay& relay, ImageEventHandlerImpl*& imageEventHandler)
{
int result = 0;
try
{
//
// Unregister image event handler
//
// *** NOTES ***
// It is important to unregister all image event handlers from the receivers
// they are registered to.
//
relay.UnregisterEventHandler(*imageEventHandler);
// Delete image event handler (because it is a pointer)
delete imageEventHandler;
cout << "Image events unregistered..." << endl << endl;
}
catch (Spinnaker::Exception& e)
{
cout << "Error: " << e.what() << endl;
result = -1;
}
return result;
}
// This functions resets the example by unregistering the image event handler.
int ResetImageEvents(CameraPtr pCam, ImageEventHandlerImpl*& imageEventHandler)
{
int result = 0;
try
{
//
// Unregister image event handler
//
// *** NOTES ***
// It is important to unregister all image event handlers from all cameras
// they are registered to.
//
pCam->UnregisterEventHandler(*imageEventHandler);
// Delete image event handler (because it is a pointer)
delete imageEventHandler;
cout << "Image events unregistered..." << endl << endl;
}
catch (Spinnaker::Exception& e)
{
cout << "Error: " << e.what() << endl;
result = -1;
}
return result;
}
// 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.
int PrintDeviceInfo(INodeMap& nodeMap)
{
int result = 0;
cout << endl << "*** DEVICE INFORMATION ***" << endl << endl;
try
{
FeatureList_t features;
CCategoryPtr category = nodeMap.GetNode("DeviceInformation");
if (IsReadable(category))
{
category->GetFeatures(features);
FeatureList_t::const_iterator it;
for (it = features.begin(); it != features.end(); ++it)
{
try
{
CNodePtr pfeatureNode = *it;
cout << pfeatureNode->GetName() << " : ";
CValuePtr pValue = (CValuePtr)pfeatureNode;
cout << (IsReadable(pValue) ? pValue->ToString() : gcstring("Node not readable"));
cout << endl;
}
catch (Spinnaker::Exception)
{
cout << "Node not readable" << endl;
}
}
}
else
{
cout << "Device control information not readable." << endl;
}
}
catch (Spinnaker::Exception& e)
{
cout << "Error: " << e.what() << endl;
result = -1;
}
return result;
}
// This function passively waits for images by calling WaitForImages(). Notice that
// this function is much shorter than the AcquireImages() function of other examples.
// This is because most of the code has been moved to the image event's OnImageEvent()
// method.
int AcquireImages(
CameraPtr pCam,
SpinnakerRelay& relay,
INodeMap& nodeMap,
INodeMap& nodeMapTLDevice,
ImageEventHandlerImpl*& imageEventHandler)
{
int result = 0;
cout << endl << endl << "*** IMAGE ACQUISITION ***" << endl << endl;
try
{
// Set acquisition mode to continuous
CEnumerationPtr ptrAcquisitionMode = nodeMap.GetNode("AcquisitionMode");
if (!IsReadable(ptrAcquisitionMode) || !IsWritable(ptrAcquisitionMode))
{
cout << "Unable to get or set acquisition mode to continuous (node retrieval). Aborting..." << endl << endl;
return -1;
}
CEnumEntryPtr ptrAcquisitionModeContinuous = ptrAcquisitionMode->GetEntryByName("Continuous");
if (!IsReadable(ptrAcquisitionModeContinuous))
{
cout << "Unable to get acquisition mode to continuous (enum entry retrieval). Aborting..." << endl << endl;
return -1;
}
int64_t acquisitionModeContinuous = ptrAcquisitionModeContinuous->GetValue();
ptrAcquisitionMode->SetIntValue(acquisitionModeContinuous);
cout << "Acquisition mode set to continuous..." << endl;
// The following camera settings are recommended for the Adimec CXP Q-21A230x camera, but may not be available
// on other cameras.
// Pixel format BayerGB8
CEnumerationPtr ptrPixelFormat = nodeMap.GetNode("PixelFormat");
// Set to BayerGB8
if (IsReadable(ptrPixelFormat) || IsWritable(ptrPixelFormat))
{
CEnumEntryPtr ptrBayerGB8 = ptrPixelFormat->GetEntryByName("BayerGB8");
if (IsReadable(ptrBayerGB8))
{
int64_t pixelFormatBayerGB8 = ptrBayerGB8->GetValue();
ptrPixelFormat->SetIntValue(pixelFormatBayerGB8);
cout << "PixelFormat set to BayerGB8..." << endl;
}
}
// Execute command to increase framerate to maximum
CCommandPtr ptrAcquisitionMaxFrameRate = nodeMap.GetNode("AcquisitionMaxFrameRate");
if (IsWritable(ptrAcquisitionMaxFrameRate))
{
ptrAcquisitionMaxFrameRate->Execute();
cout << "Acquisition framerate set to maximum" << endl;
}
// Set DeviceTapGeometry to Geometry_1X_2YE
CEnumerationPtr ptrDeviceTapGeometry = nodeMap.GetNode("DeviceTapGeometry");
if (IsReadable(ptrDeviceTapGeometry) || IsWritable(ptrDeviceTapGeometry))
{
CEnumEntryPtr ptrGeometry_1X_2YE = ptrDeviceTapGeometry->GetEntryByName("Geometry_1X_2YE");
if (IsReadable(ptrGeometry_1X_2YE))
{
ptrDeviceTapGeometry->SetIntValue(ptrGeometry_1X_2YE->GetValue());
cout << "DeviceTapGeometry set to Geometry_1X_2YE" << endl;
}
}
// Increase gain
CFloatPtr ptrGain = nodeMap.GetNode("Gain");
if (IsReadable(ptrGain) && IsWritable(ptrGain))
{
double maxGain = ptrGain->GetMax();
ptrGain->SetValue(maxGain);
cout << "Gain set to maximum value: " << ptrGain->GetValue() << endl;
}
// Execute command to calibrate white balance
CCommandPtr ptrWhiteBalanceCalibrate = nodeMap.GetNode("WhiteBalanceCalibrate");
if (IsWritable(ptrWhiteBalanceCalibrate))
{
ptrWhiteBalanceCalibrate->Execute();
cout << "White balance calibrated." << endl;
}
// Execute command to synchronize all camera image info settings to FrameGrabber image settings
//
// *** NOTES ***
// By default, the framegrabber is configured to passthrough camera image to host without further
// processing. Turn off ResyncWithRemoteDeviceAuto and configure image format settings manually
// to enable post processing on the framegrabber.
//
CCommandPtr ptrDeviceResync = nodeMapTLDevice.GetNode("ResyncWithRemoteDevice");
if (IsWritable(ptrDeviceResync))
{
ptrDeviceResync->Execute();
cout << "Device resync executed." << endl;
}
// Begin acquiring images
pCam->BeginAcquisition();
cout << "Acquiring images..." << endl;
// Retrieve images using image event handler
WaitForImages(imageEventHandler, relay);
// End acquisition
pCam->EndAcquisition();
}
catch (Spinnaker::Exception& e)
{
cout << "Error: " << e.what() << endl;
result = -1;
}
return result;
}
DWORD WINAPI PrintStatsThread(LPVOID lpParam)
{
const DWORD sleepDuration = 2000; // in milliseconds
ThreadParameters* threadParams = static_cast<ThreadParameters*>(lpParam);
SpinnakerRelay* relay = threadParams->relay;
bool isSender = threadParams->isSender;
CameraPtr pCam = threadParams->pCam;
TimeInformation* timeInfo = threadParams->timeInfo;
do
{
RelayStatistics stats;
relay->GetStatistics(&stats);
PrintRelayStatistics(&stats, timeInfo, false);
timeInfo->ResetTimeInstance();
// On sender side, check for any lost frames
if (isSender)
{
const TransportLayerStream& camStreamInfo = pCam->TLStream;
if (IsReadable(camStreamInfo.StreamLostFrameCount))
{
if (camStreamInfo.StreamLostFrameCount.GetValue() > 0)
{
cout << "\tStream Lost Frame Count: " << camStreamInfo.StreamLostFrameCount.ToString() << endl;
}
}
}
Sleep(sleepDuration);
} while (relay->IsConnected() || relay->IsConnecting());
return 0;
}
int RunReceiver()
{
int result = 0;
try
{
int err = 0;
SpinnakerRelay relay;
relay.SetConnectParameters(relayConnectParams);
relay.SetNumBuffers(500);
TimeInformation* receiverTime = new TimeInformation();
// Configure image events
ImageEventHandlerImpl* imageEventHandler;
err = ConfigureImageEvents(relay, imageEventHandler, receiverTime, saveImages);
if (err < 0)
{
return err;
}
// Connect to sender
std::cout << "Connecting to the sender...." << std::endl;
relay.Connect();
std::cout << "Sender connected!" << std::endl;
// Create stats thread
DWORD threadId;
ThreadParameters* threadParams = new ThreadParameters;
threadParams->relay = &relay;
threadParams->isSender = false;
threadParams->timeInfo = receiverTime;
HANDLE hStatsThread = CreateThread(nullptr, 0, PrintStatsThread, threadParams, 0, &threadId);
assert(hStatsThread != nullptr);
cout << endl << "*** WAITING FOR RELAYED IMAGES ***" << endl << endl;
// Retrieve images using image event handler
result = result | WaitForImages(imageEventHandler, relay);
cout << endl << "# Grabbed Images: " << imageEventHandler->getImageCount() << endl;
// Reset image events
result = result | ResetImageEvents(relay, imageEventHandler);
// Disconnect from sender
relay.Disconnect();
// Wait for stats thread to finish
WaitForSingleObject(hStatsThread, INFINITE);
CloseHandle(hStatsThread);
delete threadParams;
RelayStatistics receiverStats;
relay.GetStatistics(&receiverStats);
PrintRelayStatistics(&receiverStats, receiverTime, true);
}
catch (const std::exception& e)
{
cout << "Receiver aborted due to an error:" << e.what() << endl;
result = -1;
}
return result;
}
int RunSender(CameraPtr pCam)
{
int result = 0;
int err = 0;
try
{
// Retrieve TL device nodemap and print device information
INodeMap& nodeMapTLDevice = pCam->GetTLDeviceNodeMap();
result = PrintDeviceInfo(nodeMapTLDevice);
// Setup Spinnaker Relay
SpinnakerRelay relay;
relay.SetConnectParameters(relayConnectParams);
relay.SetNumBuffers(20);
TimeInformation* senderTime = new TimeInformation();
// Initialize camera
pCam->Init();
// Retrieve GenICam nodemap
INodeMap& nodeMap = pCam->GetNodeMap();
// Configure image events
ImageEventHandlerImpl* imageEventHandler;
err = ConfigureImageEvents(pCam, imageEventHandler, senderTime, saveImages);
if (err < 0)
{
return err;
}
// Add camera source to be forwarded
relay.AddSource(pCam);
// Connect to receiver
std::cout << "Connecting to the receiver...." << std::endl;
relay.Connect();
std::cout << "Receiver connected!" << std::endl;
// Create stats thread
DWORD threadId;
ThreadParameters* threadParams = new ThreadParameters;
threadParams->relay = &relay;
threadParams->isSender = true;
threadParams->pCam = pCam;
threadParams->timeInfo = senderTime;
HANDLE hStatsThread = CreateThread(nullptr, 0, PrintStatsThread, threadParams, 0, &threadId);
assert(hStatsThread != nullptr);
// Acquire images using the image event handler
result = result | AcquireImages(pCam, relay, nodeMap, nodeMapTLDevice, imageEventHandler);
cout << endl << "# Grabbed Images: " << imageEventHandler->getImageCount() << endl;
// Reset image events
result = result | ResetImageEvents(pCam, imageEventHandler);
// Deinitialize camera
pCam->DeInit();
// Disconnect from receiver
relay.Disconnect();
// Wait for stats thread to finish
WaitForSingleObject(hStatsThread, INFINITE);
CloseHandle(hStatsThread);
threadParams->pCam = nullptr;
delete threadParams;
// Display relay stats
RelayStatistics senderStats;
relay.GetStatistics(&senderStats);
PrintRelayStatistics(&senderStats, senderTime, true);
}
catch (Spinnaker::Exception& e)
{
cout << "Sender aborted due to an error: " << e.what() << endl;
result = -1;
}
return result;
}
// Example entry point; please see Enumeration example for additional
// comments on the steps in this function.
int main(int argc, char** argv)
{
// Since this application saves images in the current folder
// we must ensure that we have permission to write to this folder.
// If we do not have permission, fail right away.
FILE* tempFile = fopen("test.txt", "w+");
if (tempFile == nullptr)
{
cout << "Failed to create file in current folder. Please check "
"permissions."
<< endl;
cout << "Press Enter to exit..." << endl;
getchar();
return -1;
}
fclose(tempFile);
remove("test.txt");
int result = 0;
// Print application build information
cout << "Application build date: " << __DATE__ << " " << __TIME__ << endl << endl;
// Parse arguments
if (ParseArguments(argc, argv))
{
if (bReceiver)
{
RunReceiver();
}
else
{
// Retrieve singleton reference to system object
SystemPtr system = System::GetInstance();
// Print out current library version
const LibraryVersion spinnakerLibraryVersion = system->GetLibraryVersion();
cout << "Spinnaker library version: " << spinnakerLibraryVersion.major << "."
<< spinnakerLibraryVersion.minor << "." << spinnakerLibraryVersion.type << "."
<< spinnakerLibraryVersion.build << endl
<< endl;
// Retrieve list of producers and interfaces from the system
ProducerList producerList = system->GetProducers();
unsigned int numProducers = producerList.GetSize();
for (unsigned int i = 0; i < numProducers; i++)
{
cout << "Producer " << i << endl;
ProducerPtr producer = producerList.GetByIndex(i);
cout << " \tFilePath: " << producer->GetProducerFilePath() << endl;
InterfaceList interfaceList = producer->GetInterfaces();
unsigned int numInterfaces = interfaceList.GetSize();
cout << "\tNumber of interfaces detected: " << numInterfaces << endl;
CameraList camList = producer->GetCameras();
unsigned int numCameras = camList.GetSize();
cout << "\tNumber of cameras detected: " << numCameras << endl << endl;
}
// Retrieve list of cameras from the system
CameraList camList = system->GetCameras();
unsigned int numCameras = camList.GetSize();
cout << "Number of cameras detected: " << numCameras << endl << endl;
// Finish if there are no cameras
if (numCameras == 0)
{
// Clear camera list before releasing system
camList.Clear();
// Clear producer list before releasing system
producerList.Clear();
// Release system
system->ReleaseInstance();
cout << "Not enough cameras!" << endl;
cout << "Done! Press Enter to exit..." << endl;
getchar();
return -1;
}
// Create shared pointer to camera
CameraPtr pCam = nullptr;
int result = 0;
// Select camera
pCam = camList.GetByIndex(0);
cout << "Running example with camera index 0" << endl;
RunSender(pCam);
// Release reference to the camera
pCam = nullptr;
// Clear camera list before releasing system
camList.Clear();
// Clear producer list before releasing system
producerList.Clear();
// Release system
system->ReleaseInstance();
}
}
cout << endl << "Done! Press Enter to exit..." << endl;
getchar();
return result;
}