Developer Application Interface (ARC API) v3.6.2
ARC, Inc. GenII/III Application Interface
Quick Start Guide

The ARC API is a set of C++ libraries that provide classes necessary to control, aquire and post process images with the Gen II or III controller. These libraries are the hardware interface for the Owl app and may be used by developers for their own data acquisition system.

Features -

  • Compiled with GCC 11 and latest Visual Studio
  • Compatible with Linux (Ubuntu LTS 20.04 or newer) and Windows 10


General Library Requirements

C++ Version:

20 or latest

Operating Systems:

  • Windows 10
  • Linux ( Ubuntu LTS 20.04 or newer )

Supported Compilers and IDE:
  • Visual Studio Community or Professional 2022 or newer
  • GCC 11 or newer


Introduction

The Gen II/III API consists of five independent libraries and a base library, from which the libraries inherit. This means the base library must be linked into your app alongside the library being used. The base library mostly provides utility operations and exception handling.

  • CArcBase3.6.lib ............. library from which the other libraries inherit
  • CArcDevice3.6.lib ........... provides all PCI/e and controller communications
  • CArcDispaly3.6.lib .......... displays images using SAO DS9
  • CArcFitsFile3.6.lib ......... provides reading and writing FITS using cfitsio
  • CArcImage3.6.lib ............ provides image processing (such as verifying synthetic image data)
  • CArcDeinterlace3.6.lib ...... provides post-readout image de-interlacing

All controller communications are provided by the CArcDevice class located in the library with the same name. This is the primary class that an app will interact with. All other libraries, except the CArcBase library, provide some utility operation; such as writing FITS files and processing images.

The ARC Gen II/III API is written using the C++. What language features are supported is compiler dependent. Earlier revisions of C++ will be missing the necessary features to compile and link with these libraries. Also, to prevent potential run time errors, the API should be recompiled with the same compiler that the calling application is built with.




CArcDevice

The arc::gen3::CArcDevice is the class used to connect to the ARC Gen II/III PCI/e device driver and provides all PCI/e and controller comminucations.

Device class diagram:


Requirements
Headers: CArcDevice.h, CArcPCIe.h and/or CArcPCI.h
Header Folders: CArcDevice/inc,   CArcBase/inc
Libraries: CArcDevice3.6.lib/.dll/.so,   CArcBase3.6.lib/.dll/.so


Locating Devices in the System

An ARC PCI/e device must be found before it can be accessed (i.e. opened).

The following example shows how to find and print out the list of all the PCI and PCIe boards found in the system. Note that only calling findDevices() is required before opening access to one of the ARC devices. Listing or otherwise using the device list is optional.

int main( void )
{
try
{
// Find all ARC PCIe devices
// Print out the PCIe device list
auto pPCIeDevList = arc::gen3::CArcPCIe::getDeviceStringList().lock();
for ( auto i = 0U; i < arc::gen3::CArcPCIe::deviceCount(); i++ )
{
std::cout << pPCIeDevList[ i ] << '\n';
}
// Find all ARC PCI devices
// Print out the PCI device list
auto pPCIDevList = arc::gen3::CArcPCI::getDeviceStringList().lock();
for (auto i = 0U; i < arc::gen3::CArcPCI::deviceCount(); i++)
{
std::cout << pPCIDevList[ i ] << '\n';
}
}
catch ( const std::exception& e )
{
std::cerr << "\nERROR: " << e.what() << '\n';
}
return 0;
}
static void findDevices(void)
Definition: CArcPCI.cpp:136
static const std::weak_ptr< std::string[]> getDeviceStringList(void)
Definition: CArcPCI.cpp:367
static std::uint32_t deviceCount(void) noexcept
Definition: CArcPCI.cpp:348
static void findDevices(void)
Definition: CArcPCIe.cpp:297
static const std::weak_ptr< std::string[]> getDeviceStringList(void) noexcept
Definition: CArcPCIe.cpp:522
static std::uint32_t deviceCount(void) noexcept
Definition: CArcPCIe.cpp:505


All device (PCI/e) and controller communications are provided by the CArcDevice class.


Basic Device Communications and Controller Setup

The following example shows how to open communications and initialize the controller using the CArcDevice class and an ARC-66 PCIe board. To use an ARC-63/64 PCI board just replace the device instance ( new arc::gen3::CArcPCIe() ) with ( new arc::gen3::CArcPCI() )


The class methods are the same regardless of board type (PCI or PCIe) and can be switched between by instantiating the proper class as a CArcDevice object:

For PCIe: std::unique_ptr<arc::gen3::CArcDevice> pArcDev( new arc::gen3::CArcPCIe() );

For PCI: std::unique_ptr<arc::gen3::CArcDevice> pArcDev( new arc::gen3::CArcPCI() );

#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <filesystem>
#include <CArcDevice.h>
#include <CArcPCIe.h>
using namespace std::string_literals;
int main( void )
{
// Create a new CArcDevice instance
std::unique_ptr<arc::gen3::CArcDevice> pArcDev( new arc::gen3::CArcPCIe() );
if ( pArcDev != nullptr )
{
try
{
// Find the PCIe devices
// Verify that a PCIe device exists
{
throw std::runtime_error( "No PCIe devices found!" );
}
// Open the default device and map the entire image kernel buffer
pArcDev->open( 0, ( 4200 * 4200 * sizeof( std::uint16_t ) ) );
// Setup the timing file and check its existence.
std::filesystem::path tTimingFile = "/somepath/timFile.bin"s;
if ( !std::filesystem::exists( tTimingFile ) )
{
throw std::runtime_error( "File \""s + tTimingFile.string() + "\" doesn't exist!"s );
}
// Use image dimensions 2200x2048
constexpr auto uiCols = 2200U;
constexpr auto uiRows = 2048U;
// Setup the controller
pArcDev->setupController( true, // Reset controller
true, // Send test data links to controller
true, // Power-on the controller
uiRows, // Image row dimension (in pixels)
uiCols, // Image column dimension (in pixels)
tTimingFile ); // The timing (.lod) file to load
// The controller is now initialized ...
}
catch (const std::exception& e)
{
std::cerr << "\nERROR: " << e.what() << '\n';
}
}
return 0;
}
Definition: CArcPCIe.h:175


The setupController() method call in the previous example is functionally equivalent to the following:

// Reset the controller
pArcDev->resetController();
// Load the timing file
pArcDev->loadControllerFile( tTimingFile );
// Test the data link using data values 0xABCD00, 0xABCD01, ... etc
for (std::uint32_t i = 0; i < 1234; i++)
{
pArcDev->command( { arc::TIM_ID, arc::TDL, ( 0xABCD00 + i ) }, ( 0xABCD00 + i ) );
}
// Power-on the controller
pArcDev->command( { arc::TIM_ID, arc::PON }, arc::DON );
// Set the image dimensions
pArcDev->setImageSize( uiRows, uiCols );
// The controller is now initialized with all default settings ...


How to Determine Which Controller Configuration Parameters are Supported

Controller configuration parameters are used to determine what features the controller supports. The supported features can vary from one timing file (.lod) to another. Which features are available can be tested for after initializing the controller (by uploading a timing board .lod file).

A list of available controller configuration parameters can be found in the ArcDefs.h header file.

The following example shows how to verify that a specific parameter is available.

#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <CArcDevice.h>
#include <CArcPCIe.h>
#include <ArcDefs.h>
int main( void )
{
// Create a new CArcDevice pointing to an ARC-66 PCIe board instance
std::unique_ptr<arc::gen3::CArcDevice> pArcDev( new arc::gen3::CArcPCIe() );
if ( pArcDev != nullptr )
{
// ..... Initialize controller .....
std::cout << "Binning supported: \t\t" << std::boolalpha << pArcDev->isCCParamSupported(arc::BINNING) << '\n';
std::cout << "Subarray supported: \t\t" << std::boolalpha << pArcDev->isCCParamSupported( arc::SUBARRAY ) << '\n';
std::cout << "Continuous readout supported: \t" << std::boolalpha << pArcDev->isCCParamSupported( arc::CONT_RD ) << '\n';
std::cout << "ARC-22 supported: \t\t" << std::boolalpha << pArcDev->isCCParamSupported( arc::ARC22 ) << '\n';
}
return 0;
}


Basic Controller Commands and Replies

Controller commands are sent using the arc::gen3::CArcDevice command() method.

A list of the standard controller commands and reply values can be found in the ArcDefs.h header file.

Any command that does not expect a specific return value will recieve a controller reply that is represented by an ascii 'DON' (0x444F4E) on success or 'ERR' (0x455252) on error.

The following example shows how to send a command to the controller timing board.

#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <CArcDevice.h>
#include <CArcPCIe.h>
#include <ArcDefs.h>
int main( void )
{
// Create a new CArcDevice pointing to an ARC-66 PCIe board instance
std::unique_ptr<arc::gen3::CArcDevice> pArcDev( new arc::gen3::CArcPCIe() );
if ( pArcDev != nullptr )
{
try
{
// Find the PCIe devices
// Verify that a PCIe device exists
{
throw std::runtime_error( "No PCIe devices found!" );
}
// Open the default device and map the entire image kernel buffer
pArcDev->open( 0, ( 4200 * 4200 * sizeof( std::uint16_t ) ) );
// This shows how to send a test value 0x1234 to the controller timing board using the test data link (ascii 'TDL') command
constexpr auto uiTDLValue = 0x1234U;
auto uiReply = pArcDev->command( { arc::TIM_ID, arc::TDL, uiTDLValue } );
// Check the status value
if ( uiReply != uiTDLValue )
{
throw std::runtime_error( "Test data link failed ... values do not match!" );
}
// This shows how to send the TDL using the auto-check command method. This method will throw an
// exception if the received reply value doesn't match the expected value
pArcDev->command( { arc::TIM_ID, arc::TDL, uiTDLValue }, uiTDLValue );
}
catch ( const std::exception& e )
{
std::cerr << std::endl << "ERROR: " << e.what() << std::endl;
}
pArcDev->close();
}
return 0;
}


How to Take an Exposure

The following snippet shows how to start an exposure on the controller. The expose method takes an optional function for handling elapsed exposure time and readout pixel count.

#include <thread>
// ... open device and setup controller ... then ...
// Note: since the expose method blocks, we may want to put it into a seperate thread or process.
auto fnExposeRunnable = []()
{
// ...
// Start a 3.45 second exposure
pArcDev->expose( 3.45f, uiRows, uiCols );
// ...
};
std::thread tExposeThread( fnExposeRunnable );
tExposeThread.join();
// ...
// By default, the above call will not provide any feedback about the progress of the exposure or readout.
// This can be changed by passing in a pointer to a CExpIFace class, which provides callbacks for expose
// and readout that are called directly from the expose method.
class CExposeListener : public arc::gen3::CExpIFace
{
public:
CExposeListener( void ) = default;
void exposeCallback( float fElapsedTime )
{
std::cout << "elapsed time: " << fElapsedTime << '\n';
}
virtual void readCallback( std::uint32_t uiPixelCount )
{
std::cout << "pixel count: " << uiPixelCount << '\n';
}
};
auto fnExposeRunnable2 = []()
{
CExposeListener exposeListener;
// ...
pArcDev->expose( 3.45f, uiRows, uiCols, nullptr, &exposeListener );
// ...
};
std::thread tExposeThread2( fnExposeRunnable2 );
tExposeThread2.join();
// ...
Definition: CExpIFace.h:32
virtual void readCallback(const std::uint32_t uiPixelCount)=0
virtual void exposeCallback(float fElapsedTime)=0


Full Example

The following is a simple fully functional program that initializes the controller using an ARC-66 PCIe board. It then takes an exposure and prints out the first, last and middle image buffer values.

#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <filesystem>
#include <CArcDevice.h>
#include <CArcPCIe.h>
#include <ArcDefs.h>
using namespace std::string_literals;
// MainApp - Initializes the controller, updates the channel count and takes an exposure.
//
// Be sure to:
// 1. link CArcBase3.6.lib and CArcDevice3.6.lib
// 2. on Windows, define the preprocessor value _WINDOWS if not already defined
//
int main( int iArgc, char** pszArgs )
{
// Dot count for pretty output
constexpr auto uiDotCount = 70;
std::cout << std::endl;
// Create a new CArcDevice instance
std::unique_ptr<arc::gen3::CArcDevice> pArcDev( new arc::gen3::CArcPCIe );
if ( pArcDev != nullptr )
{
try
{
// Find the PCIe devices
// Verify that a PCIe device exists
{
throw std::runtime_error( "No PCIe devices found!" );
}
// Open the default device and map the entire image kernel buffer
std::cout << arc::gen3::CArcBase::setDots( "Opening device"s, uiDotCount );
pArcDev->open( 0, ( 4200 * 4200 * sizeof( std::uint16_t ) ) );
std::cout << "done!\n";
// Initialize the timing file details
auto tTimingFile = ( iArgc == 2 ? std::filesystem::path( pszArgs[ 1 ] ) : std::filesystem::path() );
if ( !std::filesystem::exists( tTimingFile ) )
{
throw std::runtime_error( "Cannot find specified file or none was given. Exiting."s );
}
std::cout << '\n';
// Define the image dimensions
constexpr auto uiCols = 2200U;
constexpr auto uiRows = 2048U;
// Setup the controller
std::cout << "Initializing controller with file : " << tTimingFile.string() << " .... ";
pArcDev->setupController( true, // Reset controller
true, // Send test data links to controller
true, // Power-on the controller
uiRows, // Image row dimension (in pixels)
uiCols, // Image column dimension (in pixels)
tTimingFile ); // The timing (.lod) file to load
std::cout << "done!\n";
// The controller is now initialized.
std::cout << "Controller initialized!\n";
// Exposure time ( in seconds )
constexpr auto fExpTime = 0.f;
// Start an exposure
std::cout << arc::gen3::CArcBase::setDots( "Starting exposure for "s + std::to_string( fExpTime ) + " seconds"s, uiDotCount );
pArcDev->expose( fExpTime, uiRows, uiCols );
std::cout << "done!\n";
// Do something with the image data
auto pU16VA = reinterpret_cast< std::uint16_t* >( pArcDev->commonBufferVA() );
std::cout << "Some image buffer data values: " << pU16VA[ 0 ] << " " << pU16VA[ ( uiCols * uiRows ) / 2 ] << " " << pU16VA[ ( uiCols * uiRows ) - 1 ] << '\n';
}
catch ( const std::exception& e )
{
std::cerr << std::endl << "ERROR: " << e.what() << '\n';
}
pArcDev->close();
}
else
{
std::cerr << std::endl << "ERROR: Failed to create an instance of CArcDevice!\n";
}
std::cout << '\n';
return 0;
}
static std::string setDots(std::string_view svText, const std::size_t uiMaxLength, const char szDot='.')
Definition: CArcBase.cpp:509

Output:

Opening device ........................................................ done!
Initializing controller with file : ti.lod .... done!
Controller initialized!
Starting exposure for 0.000000 seconds ................................ done!
Some image buffer data values: 45111 45108 45069




CArcFitsFile

The arc::gen3::CArcFitsFile is the class used to read and write FITS files. There are two available specializations of the class: one for 16-bit data (default) and one for 32-bit data.

Requirements
Headers: CArcFitsFile.h, CArcBase.h
Header Folders: CArcFitsFile/inc,   CArcBase/inc,   cfitsio-3450/include
Libraries: CArcFitsFile3.6.lib/.dll/.so,   CArcBase3.6.lib/.dll/.so


How to Read FITS

The following snippet shows how to read the first image from a 16-bit FITS data cube called "SomeFile.fts".

// Create an instance of the arc::gen3::CArcFitsFile class
std::unique_ptr<arc::gen3::CArcFitsFile<>> pFits( new arc::gen3::CArcFitsFile<>() );
if ( pFits != nullptr )
{
try
{
// Open the FITS file
pFits->open( "SomeFile.fts" );
// Read the first image in the cube.
// pImageData is a std::unique_ptr to 16-bit image data
auto pImageData = pFits->read3D( 0 );
// Do something with the data ....
// Close the file
pFits->close();
}
catch ( const std::exception& e )
{
std::cerr << "\nERROR: " << e.what() << '\n';
}
}
Definition: CArcFitsFile.h:217


How to Write FITS

The following snippet shows how to write an image to a 16-bit FITS data cube called "SomeFile.fts".

// The image properties
constexpr auto uiCols = 4096;
constexpr auto uiRows = 4096;
// Get the default image buffer from an arc::gen3::CArcDevice that was used to take an image.
auto pImageBuffer = reinterpret_cast<std::uint16_t*>( pArcDev->commonBufferVA() );
// Create an instance of the arc::gen3::CArcFitsFile class
std::unique_ptr<arc::gen3::CArcFitsFile<>> pFits( new arc::gen3::CArcFitsFile<>() );
if ( pFits != nullptr )
{
try
{
// Create the FITS file
pFits->create3D( "SomeFile.fts", uiCols, uiRows );
// Write the first image in the cube.
pFits->write3D( pImageBuffer.get() );
// Close the file
pFits->close();
}
catch ( const std::exception& e )
{
std::cerr << "\nERROR: " << e.what() << '\n';
}
}


How to Write a FITS Keyword

The following snippet shows how to write a keyword that is a decimal value called "MyValue" to a 16-bit FITS data cube called "SomeFile.fts".

// Create an instance of the arc::gen4::CArcFitsFile class
std::unique_ptr<arc::gen3::CArcFitsFile<>> pFits( new arc::gen3::CArcFitsFile<>() );
double gValue = 1.2;
if ( pFits != nullptr )
{
try
{
// Open the FITS file
pFits->open( "SomeFile.fts" );
// Write the keyword
pFits->writeKeyword( "MyValue", &gValue, arc::gen4::fits::fitsKeyType_e::FITS_DOUBLE_KEY, "This is my double value" );
// Close the file
pFits->close();
}
catch ( const std::exception& e )
{
std::cerr << "\nERROR: " << e.what() << '\n';
}
}


How to Update a FITS Keyword

The following snippet shows how to update the "MyValue" keyword from the previous section.

// Create an instance of the arc::gen3::CArcFitsFile class
std::unique_ptr<arc::gen3::CArcFitsFile<>> pFits( new arc::gen3::CArcFitsFile<>() );
double gValue = 2.1;
if ( pFits != nullptr )
{
try
{
// Open the FITS file
pFits->open( "SomeFile.fts" );
// Update the value
pFits->updateKeyword( "MyValue", &gValue, arc::gen4::fits::fitsKeyType_e::FITS_DOUBLE_KEY, "This is my NEW double value" );
// Close the file
pFits->close();
}
catch ( const std::exception& e )
{
std::cerr << "\nERROR: " << e.what() << '\n';
}
}