Thank you for choosing Diffraction Limited for your imaging needs! The following guide is to help you develop applications for controlling your Aluma brand cameras. We hope you find it straightforward and informative.
Download the installers for the DLAPI SDK here:
As of this moment the SDK consists of: the API library binaries, header files, and this documentation.
Note: This API does not support legacy SBIG ST-, STL-, STX-, STXL- and similar cameras supported by the SBIGUDrv.
Asynchronous Operation
DLAPI works on an asynchronous query and response messaging protocol that is exposed to the user via a the dl::IPromise interface class. Any API endpoint which communicates directly will return a pointer to an dl::IPromise instance which can be used to monitor the status of that message. These are divided into four types of interactions:
dl::IPromise objects report a status that notify the user when the operation they've called has completed, or thrown an error. It also stores human-readable error messages, retrieved with dl::IPromise::getLastError(), for easy troubleshooting at the call-site.
As dl::IPromise objects are created by DLAPI, their memory is owned and managed by DLAPI; It is therefore the responsibility of the user to handle dl::IPromise objects, and release them when they're done with them. This can be done by calling dl::IPromise::release() when you are done monitoring the status of a given interaction (this does not require the interaction be complete).
The example below demonstrates the workflow required to instantiate, monitor, and release a promise, as well as how to obtain any error messages from the promise (if it didn't complete successfully).
Device Interfaces
All basic components of a camera have been divided into class interfaces, such as:
- ICamera which controls camera-level settings, status information, and provides accessors to all connected peripherals.
- ISensor which controls imaging, and basic sensor-level settings.
- ITEC which controls and reports the status of the thermo-electric cooler of the camera (if available).
- IAO which controls and reports the status of any adaptive optics units connected to the camera.
- IFW which controls and reports the status of any filter wheel units connected to the camera.
Data Structures
A number of convenience structures have been created to minimize the number of calls required for obtaining basic operating parameters for cameras, sensors, and other peripherals, as well as some convenience structures for various actions (like starting an exposure). You should familiarize yourself in particular with the dl::IPromise and IImage interfaces, as well as the dl::TSubframe and dl::TExposureOptions structs.
DLAPI has a number of convenience typedefs defined for interface classes, such as dl::IImagePtr or dl::ICameraPtr for readability.
First Steps
DLAPI is accessed entirely from a single header file: dlapi.h, and all its functionality is contained within the dl namespace. Once you've added it to your project's include path, you can access your Aluma brand cameras via the dl::IGateway interface. dl::IGateway objects are instantiated and managed completely within DLAPI via the dl::getGateway() and dl::deleteGateway() functions.
The internal implementation of dl::IGateway is a singleton, and thus you can make multiple calls to dl::getGateway() safely, so long as they are accompanied by their partnered dl::deleteGateway() call—but you should get in the habit of using dependency injection, and only calling the get/delete methods once.
Once you've got an dl::IGateway instance, you can query for USB or Network cameras, and control them normally. The sample Program below searches for a connected USB camera, and obtains the camera's serial string if found. Remember that convenience typedefs exist for all pointer-to-interface classes (e.g. dl::IGateway has an accompanying dl::IGatewayPtr).
#include <iostream>
int main()
{
pGateway->queryUsbCameras();
if (pGateway->getUsbCameraCount() <= 0)
return 1;
char buf[512] = {0};
size_t lng = 512;
std::cout << std::string(&(buf[0]), lng) << std::endl;
return 0;
}
Camera interface class.
Definition dlapi.h:1632
virtual bool initialize()=0
Initializes the camera by fetching operational status, and initializing the various peripherals.
virtual void getSerial(char *buffer, size_t &buffer_length) const =0
Returns a human readable version of the camera's serial number string. e.g. "AL8300M-19010101".
Gateway object for controlling device discovery, and lifetime management.
Definition dlapi.h:2075
Main header file for DLAPI, included in client applications calling the library.
DL_API IGatewayPtr MYCDECL getGateway()
Returns a pointer to an instance of a Gateway object, or (if one exists) return the existing pointer ...
DL_API void MYCDECL deleteGateway(IGatewayPtr)
Decrement the internal count of IGateway pointers supplied, and delete the active IGateway instance w...
Querying Camera Status
The next step is to query a parameter from the camera, which requires some knowledge about how DLAPI handles Input/Output (I/O). Because the DLAPI internals use asynchronous I/O to communicate with the camera, a promise-based approach to camera control (via the dl::IPromise interface) has been implemented—as such, accessing camera parameters requires a little bit of overhead. Any camera or peripheral endpoint which returns a pointer to an dl::IPromise object executes asynchronously on a separate thread. You can use the dl::IPromise returned object to monitor the status of the command either synchronously (blocking), or asynchronously (non-blocking).
Take, for example, the example above: if instead of retrieving the camera's serial number, we wanted to retrieve the camera status, and wait until the call returned. You would need to do a few things:
{
try
{
if (!pCamera) throw std::logic_error("No camera selected")
dl::IPromisePtr pPromise = pCamera->queryStatus();
dl::IPromise::
Status result = pPromise->wait();
if (result !=
dl::IPromise::Complete)
{
char buf[512] = {0};
size_t lng = 512;
pPromise->getLastError(&(buf[0]), lng);
pPromise->release();
throw std::logic_error(std::string(&(buf[0]), lng));
}
pPromise->release();
}
catch (std::exception &ex)
{
throw std::logic_error(std::string("Cannot query the camera status: ") + ex.what() );
}
}
virtual ICamera::Status getStatus() const =0
Returns the buffered status structure.
Top-level namespace containing all of DLAPI.
Definition dlapi.h:71
It is strongly recommended that you write some custom function to handle promises for you, monitoring their status and releasing them when they're complete. Such a function might look something like this:
{
if (result != IPromise::Complete)
{
char buf[512] = {0};
size_t lng = 512;
throw std::logic_error(std::string(&(buf[0]), lng));
}
}
Promise interface class.
Definition dlapi.h:537
Status
Definition dlapi.h:545
virtual Status wait()=0
Wait (blocking) until the IPromise either completes, or returns an error (timing out at 10 seconds si...
virtual void release()=0
Flags the promise for release within DLAPI.
virtual void getLastError(char *buffer, size_t &bufferSize) const =0
Puts a human readable string into the supplied buffer containing the last known error for command the...
Controlling Your Camera's Peripherals
Many of the settings you'll need to access during the normal operation of your camera are accessible via one of the camera's peripheral interfaces. Controlling cooling uses the dl::ITEC interface, whereas controlling the imaging sensor is done via an dl::ISensor interface. These are accessible via the dl::ICamera::getTEC() and dl::ICamera::getSensor() commands respectively.
Let's attempt to change the fan speed for the main sensor's fan. This can be achieved by updating the value of the dl::ISensor::Settings::FanSpeed setting:
void setFanSpeed(
ICameraPtr pCamera,
int fanSpeed)
{
try
{
if (!pSensor) return;
handlePromise(pPromise);
}
catch (std::exception &ex)
{
std::ostringstream oss;
oss << "Failed to set fan speed to: " << fanSpeed << std::endl << ex.what();
}
}
virtual ISensorPtr getSensor(unsigned int id) const =0
Returns a pointer to the indexed sensor (zero is always the primary imaging chip).
Sensor Interface Class.
Definition dlapi.h:973
virtual IPromisePtr setSetting(ISensor::Setting key, int value)=0
Update an ISensor::Setting value in the camera by key.
Your First Image
Acquiring your first image follows much of the same setup as before, and requires only a little extra overhead to get the job done. First, we need to know the dimensions of the sensor we're imaging with:
{
try
{
if (!pSensor) throw std::logic_error("No sensor present");
}
catch(std::exception &ex)
{
throw std::logic_error(std::string("Failed to retrieve Sensor information: ") + ex.what());
}
return info;
}
virtual IPromisePtr queryInfo()=0
Query the camera's ISensor::Info structure.
virtual ISensor::Info getInfo() const =0
Returns the buffered ISensor::Info structure.
A data structure containing sensor specifications.
Definition dlapi.h:1103
The dl::ISensor::Info structure contains the dl::ISensor::Info::pixelsX and dl::ISensor::Info::pixelsY parameters, which represent the full height and width of the sensor in pixels. Next, we need to construct a subframe for the image. In this case, let's take a full-frame image, binned 1x1.
{
try
{
if (!pSensor) throw std::logic_error("No sensor present");
}
catch (std::exception &ex)
{
throw std::logic_error(std::string("Failed to set subframe: ") + ex.what());
}
}
virtual IPromisePtr setSubframe(const TSubframe &value)=0
Update the camera's active subframe.
unsigned int pixelsY
The sensor's pixel count in the y-axis. Changes when ISensor::Setting::UseOverscan is toggled.
Definition dlapi.h:1107
unsigned int pixelsX
The sensor's pixel count in the x-axis. Changes when ISensor::Setting::UseOverscan is toggled.
Definition dlapi.h:1106
A collection of frame parameters, required for specifying the dimensions of an image.
Definition dlapi.h:275
int height
The frame's height in pixels.
Definition dlapi.h:279
int binX
The frame's x-binning in pixels.
Definition dlapi.h:280
int top
The frame's y-offset from the sensor's origin in pixels.
Definition dlapi.h:276
int left
The frame's x-offset from the sensor's origin in pixels.
Definition dlapi.h:277
int width
The frame's width in pixels.
Definition dlapi.h:278
int binY
The frame's y-binning in pixels.
Definition dlapi.h:281
With the subframe armed, we can start the exposure. For this example, let's set up a 1 second light frame exposure.
{
try
{
if (!pSensor) throw std::logic_error("No sensor present");
handlePromise(pSensor->StartExposure(options));
}
catch (std::exception &ex)
{
throw std::logic_error(std::string("Failed to start exposure: ") + ex.what());
}
}
A collection of exposure parameters required for starting an exposure.
Definition dlapi.h:257
char binX
[[deprecated]] Binning of the exposure in the x-direction in pixels. Is unused, and will go away in f...
Definition dlapi.h:260
bool useRBIPreflash
true to perform an RBI Preflash operation (if the camera supports it) before the exposure....
Definition dlapi.h:263
float duration
Duration of the exposure in seconds.
Definition dlapi.h:258
unsigned int readoutMode
The index of the readout mode to use in the exposure. This is the index of the readout mode chosen fr...
Definition dlapi.h:259
char binY
[[deprecated]] Binning of the exposure in the y-direction in pixels. Is unused, and will go away in f...
Definition dlapi.h:261
bool isLightFrame
true if the exposure is a light frame, false otherwise.
Definition dlapi.h:262
bool useExtTrigger
true to wait for an external trigger before executing the exposure. Consult your camera's user manual...
Definition dlapi.h:264
Now it's our responsibility to wait until the camera's sensor reports ready for download. We can do that using the getCameraStatus() function we wrote above, and a bit of business logic:
{
ISensor::Status sensorStatus = ( sensorId == 0 ) ? cameraStatus.mainSensorState : cameraStatus.extSensorState;
return sensorStatus == ISensor::ReadyToDownload;
}
Status
Definition dlapi.h:1023
virtual const unsigned int getSensorId() const =0
Returns the sensor index of the specified sensor (within ICamera)
Now we can start the download proper using a call to dl::ISensor::startDownload(), but because this can take some time (several seconds depending on the sensor and its digitization rate), it's wise to write a promise handling function that is non-blocking, so let's do that here:
bool isXferComplete(
IPromise * pPromise)
{
if (status == IPromise::Complete)
{
return true;
}
else if (status == IPromise::Error)
{
char buf[512] = {0};
size_t blng = 512;
throw std::logic_error(&(buf[0]));
}
return false;
}
virtual Status getStatus() const =0
Retrieve the status of the command the IPromise is tracking.
Once that function returns true, we can retrieve the image buffer from the camera. It's good practice to make a local copy of the buffer as soon as you retrieve it as follows:
#include <algorithm>
void copyImageBuffer(
ISensorPtr pSensor,
unsigned short * pBuffer,
size_t bufferLength)
{
auto cpyLng = sizeof(unsigned short) * std::min<size_t>(bufLng, bufferLength);
memmove(pBuffer, pImgBuf, cpyLng);
}
Interface to an DLAPI Image buffer.
Definition dlapi.h:405
virtual unsigned short * getBufferData() const =0
Returns a pointer to the raw data buffer of the image.
virtual unsigned int getBufferLength() const =0
Returns the length in pixels of the raw data buffer of the image.
virtual IImagePtr getImage() const =0
Returns the ISensor's buffered image.
Now let's tie this all together from start to finish:
void takeImage(
ICameraPtr pCamera,
unsigned int sensorId,
unsigned short *& pBuffer,
size_t &bufferLength)
{
pBuffer = nullptr;
bufferLength = 0;
if (!pCamera) return;
if (!pSensor) return;
try
{
setSubframe(pSensor, info);
startExposure(pSensor);
while (!isImageReadyForDownload(pCamera, pSensor))
{
}
while (!isXferComplete(pImgPromise))
{
}
pBuffer = new unsigned short[bufferLength];
copyImageBuffer(pSensor, pBuffer, bufferLength);
}
catch (std::exception &ex)
{
logMessage(std::string("Failed to take image: ") + ex.what());
}
}
virtual IPromisePtr startDownload()=0
Start downloading an image from the camera to DLAPI's internal buffers.
virtual IPromisePtr abortExposure()=0
Abort any exposures/downloads either in progress, or waiting for user input.
And that's the basics of camera control. See the ICamera, ISensor, ITEC, IFW, and IAO classes for more details on how to control your camera and its peripherals.
Cooler Control Best Practices
Cameras with coolers can be controlled via the ITEC interface, and there are some considerations we advise application developers in following when using the ITEC interface.
Initialization
After retrieving a pointer to an ICamera object from the Gateway, you need to check whether the camera supports the use of a cooler. This is achieved via the ICamera::Capability
API.
{
if (!pCamera) return false;
auto supportsCooler = false;
try
{
handlePromise(pPromise);
supportsCooler = pCamera->
getCapability(ICamera::eSupportsCooler);
}
catch (std::exception & ex)
{
logMessage("Failed to query camera capabilities: " + ex.what());
}
return supportsCooler;
}
virtual IPromisePtr queryCapability(ICamera::Capability key)=0
Queries a camera for a specific capability.
virtual bool getCapability(ICamera::Capability key) const =0
Returns the buffered value of a successful call to ICamera::queryCapability()
Assuming the camera you've connected to supports a cooler, we advise retrieving the min/max cooler setpoints from the sensor info:
void getCoolerMinMax(
ISensorPtr pSensor,
int & min,
int & max)
{
min = INT_MIN;
max = INT_MAX;
try
{
handlePromise(pPromise);
min = minCoolerSetpoint;
max = maxCoolerSetpoint;
}
catch (std::exception &ex)
{
logMessage("Failed to query sensor info: " + ex.what());
}
}
Once you know the minimum and maximum cooler setpoints, and whether the camera supports the use of a cooler, you can control the cooler via the ITEC interface after you've called ICamera::initialize()
.
Some special considerations are necessary: The majority of sensors recommend not exceeding temperature changes greater than 10 degrees Celsius per minute (especially for extended periods of time). This is especially true for cameras with high cooling power (i.e. greater than 50 C changes). We advise slowly ramping up/down the temperature in 5 degree increments every 30 seconds until you reach the desired setpoint. Cameras with high cooling power perform this function automatically, but it never hurts to be cautious.
{
if (!pCamera) throw std::invalid_argument("Camera pointer was null");
if (!pSensor) throw std::invalid_argument("Sensor pointer was null");
if (!supportsCooler(pCamera)) throw std::runtime_error("Camera does not support the use of a cooler");
auto pTEC = pCamera->
getTEC();
if (!pTEC) throw std::runtime_error("TEC was not found in Camera interface");
int minSetpoint, maxSetpoint;
getCoolerMinMax(pSensor, minSetpoint, maxSetpoint);
if (setpoint < minSetpoint) throw std::invalid_argument("Provided setpoint is below minimum cooler capability");
if (setpoint > maxSetpoint) throw std::invalid_argument("Provided setpoint is above maximum cooler capability");
try
{
auto pPromise = pTEC->setState(enable, setpoint);
handlePromise(pPromise);
}
catch (std::exception &ex)
{
logMessage("Failed to set the cooler's setpoint: " + ex.what());
}
}
virtual ITECPtr getTEC() const =0
Returns a pointer to the camera's TEC (if one is available).
During normal operation, the cooler status is reported as part of the ICamera::Status
object. You can retrieve that value using a normal camera status query. Values of note include: ICamera::Status::coolerPower
which measures the duty cycle of the cooler's power consumption in percent, ICamera::Status::sensorTemperature
which measures the temperature of the sensor in degrees Celsius (if ICamera::Capability::eSupportsCoolerTemp
is true
), and ICamera::Status::heatSinkTemperature
which measures the camera's heatsink temperature in degrees Celsius (if ICamera::Capability::eSupportsHeatSinkTemp
is true
).
When shutting down the cooler, you make the call to ITEC::setState()
with the first parameter set to false
. You can set the cooler setpoint to whatever temperature you like (ambient, 0, etc) as it is ignored by the camera. Remember to ramp up the temperature to prevent sensor damage.