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
{
{
std::cout << pPCIeDevList[ i ] << '\n';
}
{
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>
using namespace std::string_literals;
int main( void )
{
if ( pArcDev != nullptr )
{
try
{
{
throw std::runtime_error( "No PCIe devices found!" );
}
pArcDev->open( 0, ( 4200 * 4200 * sizeof( std::uint16_t ) ) );
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 );
}
constexpr auto uiCols = 2200U;
constexpr auto uiRows = 2048U;
pArcDev->setupController( true,
true,
true,
uiRows,
uiCols,
tTimingFile );
}
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:
pArcDev->resetController();
pArcDev->loadControllerFile( tTimingFile );
for (std::uint32_t i = 0; i < 1234; i++)
{
pArcDev->command( { arc::TIM_ID, arc::TDL, ( 0xABCD00 + i ) }, ( 0xABCD00 + i ) );
}
pArcDev->command( { arc::TIM_ID, arc::PON }, arc::DON );
pArcDev->setImageSize( uiRows, uiCols );
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>
int main( void )
{
if ( pArcDev != nullptr )
{
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>
int main( void )
{
if ( pArcDev != nullptr )
{
try
{
{
throw std::runtime_error( "No PCIe devices found!" );
}
pArcDev->open( 0, ( 4200 * 4200 * sizeof( std::uint16_t ) ) );
constexpr auto uiTDLValue = 0x1234U;
auto uiReply = pArcDev->command( { arc::TIM_ID, arc::TDL, uiTDLValue } );
if ( uiReply != uiTDLValue )
{
throw std::runtime_error( "Test data link failed ... values do not match!" );
}
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>
auto fnExposeRunnable = []()
{
pArcDev->expose( 3.45f, uiRows, uiCols );
};
std::thread tExposeThread( fnExposeRunnable );
tExposeThread.join();
{
public:
CExposeListener( void ) = default;
{
std::cout << "elapsed time: " << fElapsedTime << '\n';
}
{
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>
using namespace std::string_literals;
int main( int iArgc, char** pszArgs )
{
constexpr auto uiDotCount = 70;
std::cout << std::endl;
if ( pArcDev != nullptr )
{
try
{
{
throw std::runtime_error( "No PCIe devices found!" );
}
pArcDev->open( 0, ( 4200 * 4200 * sizeof( std::uint16_t ) ) );
std::cout << "done!\n";
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';
constexpr auto uiCols = 2200U;
constexpr auto uiRows = 2048U;
std::cout << "Initializing controller with file : " << tTimingFile.string() << " .... ";
pArcDev->setupController( true,
true,
true,
uiRows,
uiCols,
tTimingFile );
std::cout << "done!\n";
std::cout << "Controller initialized!\n";
constexpr auto fExpTime = 0.f;
pArcDev->expose( fExpTime, uiRows, uiCols );
std::cout << "done!\n";
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".
if ( pFits != nullptr )
{
try
{
pFits->open( "SomeFile.fts" );
auto pImageData = pFits->read3D( 0 );
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".
constexpr auto uiCols = 4096;
constexpr auto uiRows = 4096;
auto pImageBuffer = reinterpret_cast<std::uint16_t*>( pArcDev->commonBufferVA() );
if ( pFits != nullptr )
{
try
{
pFits->create3D( "SomeFile.fts", uiCols, uiRows );
pFits->write3D( pImageBuffer.get() );
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".
double gValue = 1.2;
if ( pFits != nullptr )
{
try
{
pFits->open( "SomeFile.fts" );
pFits->writeKeyword( "MyValue", &gValue, arc::gen4::fits::fitsKeyType_e::FITS_DOUBLE_KEY, "This is my double value" );
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.
double gValue = 2.1;
if ( pFits != nullptr )
{
try
{
pFits->open( "SomeFile.fts" );
pFits->updateKeyword( "MyValue", &gValue, arc::gen4::fits::fitsKeyType_e::FITS_DOUBLE_KEY, "This is my NEW double value" );
pFits->close();
}
catch ( const std::exception& e )
{
std::cerr << "\nERROR: " << e.what() << '\n';
}
}