The ARC API is a set of modern C++ libraries that provide classes necessary to control, aquire and post process images with the Gen IV controller. These libraries are the hardware interface for the G4 app and may be used by developers for their own data acquisition system.
Features -
-
Uses modern C++ features from GCC 11 and latest Visual Studio
-
Visual Studio Code is now the Linux development environment
-
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 2019 or newer
-
Visual Studio Code and GCC 11 or newer
|
Introduction
The Gen IV 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.
-
CArcBase4.5.lib ......... library from which the other libraries inherit
-
CArcDevice4.5.lib ....... provides all PCIe and controller communications
-
CArcDispaly4.5.lib ...... displays images using SAO DS9
-
CArcFitsFile4.5.lib ..... provides reading and writing FITS using cfitsio
-
CArcImage4.5.lib ........ provides image processing (such as verifying synthetic image data)
-
CArcVersion4.5.lib ...... provides version details for the API
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 IV API is written using the latest C++ features available (currently C++20). What 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::gen4::CArcDevice is the class used to connect to the ARC Gen IV PCIe device driver and provides all PCIe and controller comminucations.
Requirements |
Headers: | CArcDevice.h, CArcBase.h |
Header Folders: | CArcDevice/inc, CArcBase/inc |
Libraries: | CArcDevice4.5.lib/.dll/.so, CArcBase4.5.lib/.dll/.so |
All device (PCIe) and controller communications are provided by the CArcDevice class.
Basic Controller Commands and Status
Controller commands are sent using one of the arc::gen4::CArcDevice command() methods. Most of these commands return a status packet, defined by arc::gen4::CArcStatusPacket, containing the controller reply. The returned reply can be obtained by calling the status packet value() method. This method is indexed to allow access to replies that consist of multiple reply values.
A list of the standard (non-array specific) controller commands and status values can be found in the ArcCommandDefs.h header file.
Many commands expect a arc::gen4::cmds_e::DONE (0x444F4E45) as a reply for success. This can be simply checked by calling the status packet success() method, which will return true if the reply has a done value or false otherwise.
A few of the command() methods are designed to auto check the return value against an expected value (passed as a parameter to command()) and throw an exception if they do not match. This is a more compact way to verify a reply value.
Every packet (control, status, image) has the same basic format.
The payload format depends on the type of packet. For status packets (i.e. replies), the payload format consists of the command that generated the status; followed by any status (reply) values. For example, the following command to write the image dimensions, 'DIM' 1024 1024, will return the following payload in the status packet: 'DIM' 'DONE'.
The following example shows the many ways to send a controller command.
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <CArcDevice.h>
#include <ArcCommandDefs.h>
int main( void )
{
if ( pArcDev != nullptr )
{
try
{
pArcDev->open();
const auto uiTDLValue = static_cast<std::uint32_t>( 0xABCD0001 );
if ( !pStatusPacket->compare( uiTDLValue ) )
{
throw std::runtime_error( "Test data link failed ... values do not match!" );
}
if ( !pStatusPacket->compare( uiTDLValue ) )
{
throw std::runtime_error( "Test data link failed ... values do not match!" );
}
pStatusPacket = pArcDev->command( std::vector{
static_cast< std::uint32_t
>(
arc::gen4::cmds_e::TDL ), uiTDLValue } );
if ( !pStatusPacket->compare( uiTDLValue ) )
{
throw std::runtime_error( "Test data link failed ... values do not match!" );
}
if ( !pStatusPacket->compare( uiTDLValue ) )
{
throw std::runtime_error( "Test data link failed ... values do not match!" );
}
pArcDev->command( std::vector{
static_cast< std::uint32_t
>(
arc::gen4::cmds_e::TDL ), uiTDLValue }, uiTDLValue );
if ( !pStatusPacket->success() )
{
throw std::runtime_error( "Failed to set image dimensions!" );
}
if ( pCommandPacket != nullptr )
{
pStatusPacket = pArcDev->command( pCommandPacket.get() );
if ( !pStatusPacket->compare( uiTDLValue ) )
{
throw std::runtime_error( "Test data link failed ... values do not match!" );
}
}
}
catch ( const std::exception& e )
{
std::cerr << std::endl << "ERROR: " << e.what() << std::endl;
}
}
}
static CArcPacketFactory & getInstance(void) noexcept
std::unique_ptr< CArcCommandPacket > cmdPacket(const std::uint8_t uiId=0) const
Basic Device Communications and Controller Setup
The following example shows how to open communications and initialize the controller using the CArcDevice class.
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <filesystem>
#include <CArcBase.h>
#include <CArcDevice.h>
using namespace std::string_literals;
int main( void )
{
if ( pArcDev != nullptr )
{
try
{
pArcDev->open();
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 );
}
pArcDev->setup( tTimingFile );
pArcDev->setImageDimensions( 4096, 2048 );
}
catch ( const std::exception& e )
{
std::cerr << std::endl << "ERROR: " << e.what() << std::endl;
}
}
}
The setup() method call in the previous example is functionally equivalent to the following:
pArcDev->loadFile( tTimingFile );
for ( std::uint32_t i = 0; i < 1234; i++ )
{
}
pArcDev->setImageDimensions( 4096, 2048 );
How to Get the Controller Command List
The following snippet shows how to obtain the string list of available commands from the controller.
auto pCmdList = pArcDev->getCommandList();
if ( pCmdList.get() != nullptr )
{
for ( std::uint32_t i = 0; i < pCmdList->length(); i++ )
{
std::cout << pCmdList->at( i ) << std::endl;
}
}
Output:
ARC-440 ADC mode control | AMC <boardNumber> <4-bit value>
Assign virtual channels | AVC <boardNumber> [<enableBits> <virtToPhy 0> ...]
Board revision info | BRI <boardNumber>
Change exposure time | CET <10s of microseconds>
Get/Set image dimensions |
DIM [<columns> <rows>]
Assign
default video channels |
DVC
.... etc ....
How to Take an Exposure
The following snippet shows how to start an exposure on the controller. The expose method takes optional functions for handling elapsed exposure time, readout pixel count and current system state. By default, unless provided as a parameter, the expose method will issue a command to the controller to read the current system state, which will be used during the exposure/readout and returned in the parameter if it's not null.
#include <thread>
auto fnExposeRunnable = []()
{
auto pImageBuffer = pArcDev->expose( 3450 );
if ( pImageBuffer != nullptr && pImageBuffer->pVirtualAddress != nullptr && pImageBuffer->uiSize > 0 )
{
}
};
std::thread tExposeThread( fnExposeRunnable );
tExposeThread.join();
auto fnExposeListener = []( float fElapsedTime )
{
std::cout << "Elapsed time: " << fElapsedTime << std::endl;
};
auto fnReadListener = []( std::uint32_t uiPixelCount, float fReadTime )
{
std::cout << "Pixel count: " << uiPixelCount << std::endl;
};
auto fnExposeRunnable2 = []()
{
auto pImageBuffer = pArcDev->expose( 3450, fnExposeListener, fnReadListener );
if ( pImageBuffer != nullptr && pImageBuffer->pVirtualAddress != nullptr && pImageBuffer->uiSize > 0 )
{
}
};
std::thread tExposeThread2( fnExposeRunnable2 );
tExposeThread2.join();
auto pImageBuffer = pArcDev->expose( 3450, fnExposeListener, fnReadListener, pSysState );
GEN4_CARCDEVICE_API const std::string sysStateToString(pSysState_t pSysState)
std::shared_ptr< sysState_t > pSysState_t
How to Modify Video Channel Selection
The following snippet shows how to modify the default channel selection. There are two types of operations, the first is to enable or disable specific physical video channels. The second is to assign new virtual channel numbers to the physical channels.
uiCols = 4096;
uiRows = 4096;
uwChannelCount = 2;
uiBoardNumber = 9;
try
{
pArcDev->verifyColumnChannelCount( uiCols, uwChannelCount );
std::uint32_t uiEnableBits = 0x4008;
std::uint32_t uiVirt0ToPhys3 = ( ( 3 << 16 ) | 0 );
std::uint32_t uiVirt1ToPhys14 = ( ( 14 << 16 ) | 0 );
pArcDev->command(
static_cast<std::uint32_t
>(
arc::gen4::cmds_e::AVC ), uiBoardNumber, uiEnableBits, uiVirt0ToPhys3, uiVirt1ToPhys14 );
}
catch ( const std::exception& e )
{
std::cerr << std::endl << "ERROR: " << e.what() << std::endl;
}
How to Enable HxRG Windowing Mode
The following snippet shows how to HxRG windowing mode. The windowing channel is fixed and dependent on which HxRG device is being used. The H1RG and H2RG use channel 7, while the H4RG uses channel 15. This means only the one channel should be enabled on the controller and all others must be disabled.
constexpr auto SWM = 0x0053574DU;
auto uiRowStart = 100U;
auto uiRowEnd = 500U;
auto uiColStart = 100U;
auto uiColEnd = 227U;
try
{
" ] x [ "s + std::to_string( uiRowEnd - uiRowStart ) + " ]"s, uiDotCount );
auto pStatusPacket = pArcDev->command( SWM, 7U, uiRowStart, uiRowEnd, uiColStart, uiColEnd );
std::cout << "done!" << std::endl;
if ( !pStatusPacket->success() )
{
}
auto pBoardMap = pArcDev->getBoardMap();
auto pArc440List = pBoardMap->findBoard( 440 );
auto pLocked = pArc440List.lock();
std::cout << std::endl;
std::for_each( pLocked->cbegin(), pLocked->cend(), [ &pStatusPacket ]( const auto& tElem )
{
pStatusPacket = pArcDev->command( arc::gen4::cmds_e::EVC, tElem );
std::cout << "Channels enabled on board #"sv << ( tElem < 10 ? "0"sv : ""sv ) << tElem << " -> 0x"sv << std::hex
<< std::setw( 4 ) << std::setfill( '0' ) << pStatusPacket->value() << std::dec << std::endl;
} );
std::cout << std::endl;
for ( auto it = pLocked->cbegin(); it != pLocked->cend(); it++ )
{
for ( decltype( pStatusPacket->valueCount() ) i = 0; i < pStatusPacket->valueCount(); i++ )
{
{
std::cout << "Assigned video channel on board #" << ( *it < 10 ? "0"sv : ""sv ) << *it
<< " -> physical " << ( ( pStatusPacket->value( i ) >> 16 ) & 0xFF )
<< " set to virtual " << ( pStatusPacket->value( i ) & 0xFFFF ) << std::endl;
}
}
}
std::cout << std::endl;
}
catch ( const std::exception& e )
{
std::cerr << std::endl << "ERROR: " << e.what() << std::endl;
}
static std::string setDots(const std::string &sText, const std::size_t uiMaxLength, const char szDot='.')
static constexpr auto ARC440_INVALID_CHANNEL_ADDRESS
void throwArcGen4Error(const throwFormat_t<> tFormat, Args &&... args)
Full Example
The following is a simple fully functional program that initializes the controller, 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 <CArcBase.h>
#include <CArcDevice.h>
using namespace std::string_literals;
int main( void )
{
constexpr auto uiDotCount = 70;
std::cout << std::endl;
if ( pArcDev != nullptr )
{
try
{
pArcDev->open();
std::cout << "done!" << std::endl;
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 << std::endl;
std::cout << "Initializing controller with file : " << tTimingFile.string() << " .... ";
pArcDev->setup( tTimingFile );
std::cout << "done!" << std::endl;
std::cout << "Controller initialized!" << std::endl;
constexpr auto uiCols = static_cast<std::uint32_t>( 4096 );
constexpr auto uiRows = static_cast<std::uint32_t>( 2048 );
pArcDev->setImageDimensions( uiCols, uiRows );
std::cout << std::endl;
constexpr auto uiExpTimeMs = static_cast< std::uint32_t >( 0 );
auto pImageBuffer = pArcDev->expose( uiExpTimeMs );
std::cout << "done!" << std::endl;
if ( pImageBuffer != nullptr && pImageBuffer->pVirtualAddress != nullptr && pImageBuffer->uiSize > 0 )
{
std::cout << "Image buffer: " << std::endl << pImageBuffer->toString() << std::endl;
auto pU16VA = reinterpret_cast< std::uint16_t* >( pImageBuffer->pVirtualAddress.get() );
std::cout << "Some data values: " << pU16VA[ 0 ] << " " << pU16VA[ ( uiCols * uiRows ) / 2 ] << " " << pU16VA[ ( uiCols * uiRows ) - 1 ] << std::endl;
}
else
{
throw std::runtime_error( "Invalid image buffer [ nullptr ]!"s );
}
}
catch ( const std::exception& e )
{
std::cerr << std::endl << "ERROR: " << e.what() << std::endl;
}
}
else
{
std::cerr << std::endl << "ERROR: Failed to create an instance of CArcDevice!" << std::endl;
}
std::cout << std::endl;
}
Output:
Opening device ........................................................ done!
Initializing controller with file : H1RG\Debug\H1RG.bin .... done!
Controller initialized!
Starting exposure for 0 seconds ....................................... done!
Image buffer:
Virtual address ............... 0x216a3aea040
Size .......................... 38720000
Some data values: 45111 45108 45069
CArcFitsFile
The arc::gen4::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. Currently, GenIV only produces 16-bit data.
Requirements |
Headers: | CArcFitsFile.h, CArcBase.h |
Header Folders: | CArcFitsFile/inc, CArcBase/inc, cfitsio-3450/include |
Libraries: | CArcFitsFile4.5.lib/.dll/.so, CArcBase4.5.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 << std::endl << "ERROR: " << e.what() << std::endl;
}
}
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 = pArcDev->getImageBuffer();
if ( pFits != nullptr )
{
try
{
pFits->create3D( "SomeFile.fts", uiCols, uiRows );
pFits->write3D( pImageBuffer->pVirtualAddress.get() );
pFits->close();
}
catch ( const std::exception& e )
{
std::cerr << std::endl << "ERROR: " << e.what() << std::endl;
}
}
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->close();
}
catch ( const std::exception& e )
{
std::cerr << std::endl << "ERROR: " << e.what() << std::endl;
}
}
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->close();
}
catch ( const std::exception& e )
{
std::cerr << std::endl << "ERROR: " << e.what() << std::endl;
}
}
CArcImage
The arc::gen4::CArcImage is the class used for basic image processing, such as filling an image buffer with a specific pattern, calculating statistics (min, max, mean, standard deviation), data histogram, read rows or columns and more. The classes are static based and cannot be instantiated. There are two available specializations of the class: one for 16-bit data (default) and one for 32-bit data. Currently, GenIV only produces 16-bit data.
Requirements |
Headers: | CArcImage.h, CArcBase.h |
Header Folders: | CArcImage/inc, CArcBase/inc |
Libraries: | CArcImage4.5.lib/.dll/.so, CArcBase4.5.lib/.dll/.so |
How to Fill a Buffer With a Specific Value
The following snippet shows how to fill the kernel common buffers with a specific value
auto pImageBuffer = pArcDev->expose( 0 );
static auto fill(T *pBuf, const std::uint32_t uiCols, const std::uint32_t uiRows, const T uiValue) -> void
How to Verify an ARC-440 Synthetic Image
The following snippet shows how to verify that the data in the returned image buffer is a valid ARC-440 synthetic image.
auto pCommonBuffers = pArcDev->getCommonBuffers();
auto pSystemState = pArcDev->getSystemState();
static auto containsValidArc440Synthetic(const T *pBuf, const std::uint32_t uiPixelsPerChannel, const std::uint32_t uiCols, const std::uint32_t uiRows) -> void